일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | 3 | 4 | |||
5 | 6 | 7 | 8 | 9 | 10 | 11 |
12 | 13 | 14 | 15 | 16 | 17 | 18 |
19 | 20 | 21 | 22 | 23 | 24 | 25 |
26 | 27 | 28 | 29 | 30 | 31 |
- privacyinfo.plist
- 라이브러리 없이
- 명시적 정체성
- launchscreen
- 360도 뷰어
- SwiftUI
- panorama view
- ios
- Android
- data driven construct
- 구조적 정체성
- ssot
- 스켈레톤 통합
- 뷰 생명주기
- 네이티브
- 리액트 네이티브
- 360도 이미지
- React Native
- launch screen
- 파노라마 뷰
- React-Native
- react-native-fast-image
- 3b52.1
- native
- 360도 이미지 뷰어
- 리액트
- react
- 앱 성능 개선
- 뷰 정체성
- requirenativecomponent
- Today
- Total
Neoself의 기술 블로그
CS 정리 (10월 22일) 본문
컴퓨터 시스템의 구성요소
CPU
컴퓨터의 두뇌 역할을 하는 중앙처리장치로, 실제 연산과 명령어 처리를 담당합니다. 산술논리연산장치(ALU)와 제어장치(CU)로 구성되어 있습니다.
RAM
주기억장치로, CPU가 직접 접근할 수 있는 고속의 휘발성 메모리입니다. 실행 중인 프로그램과 데이터를 임시로 저장합니다.
저장장치
HDD나 SSD와 같은 비휘발성 메모리로, 전원이 꺼져도 데이터가 유지됩니다.
CPU와 메모리 간의 데이터 교환은 버스 시스템을 통해 이루어집니다. 여기서 버스는 전기적 신호를 전달하는 물리적 와이어들의 집합이라고 이해하면 쉽습니다. 버스는 아래와 같은 유형으로 나뉩니다.
- 데이터 버스: 양방향 통신을 지원하며, CPU 및 다른 장치들 사이의 실제 데이터를 전송합니다.
- 주소 버스(Address Bus): CPU -> 다른 장치로 가는 단방향 통신을 지원하며, 메모리나 I/O 장치의 특정 주소를 지정할때 사용됩니다. 예를 들어 CPU가 메모리의 특정 데이터를 읽으려 할때에는, 참조하고자 하는 메모리 주소를 주소 버스를 통해 메모리로 전송, 지정된 주소의 데이터를 메모리가 데이터 버스를 통해 CPU로 반환하는 과정을 거치며, 반대로 데이터를 쓰고자 할때에는 주소버스를 먼저 전송한후, 다시 CPU가 대상 장치에 데이터버스를 사용해 데이터를 전송하는 과정을 거칩니다. 하지만, 읽기와 쓰기 둘다 메모리의 주소를 전송하는 주소 버스를 사용한다는 점에서, 대상 장치에서는 어떤 동작을 수행해야하는지 알수가 없습니다. 이때 제어버스가 필요하게 됩니다.
- 제어 버스(Control Bus): 참조하고자 하는 주소, 데이터를 통해 어떤 작업을 할 계획인지 전달하는 데에 사용되는 버스입니다. 이러한 제어 동작은 CPU 뿐 아니라, 인터럽트와 같은 다른 장치에서도 통신이 시작될 수 있는 것이기 때문에, 양방향 통신을 지원합니다.
캐시 메모리와 지역성
캐시 메모리는 CPU와 주기억장치(RAM) 사이의 속도 차이를 줄이기 위한 고속의 버퍼 메모리입니다.
지역성 원리는 캐시의 효율성을 높이는 핵심 개념으로, 두 가지 유형이 있습니다:
- 시간적 지역성: 최근 접근한 데이터는 곧 다시 접근될 가능성이 높다는 원리
- 공간적 지역성: 특정 데이터 주변의 데이터도 곧 접근될 가능성이 높다는 원리
보다 지역성의 원리를 쉽게 파악하기 위해선 아래의 코드를 보시면 됩니다.
int arr[1000];
int sum = 0;
for(int i = 0; i < 1000; i++) {
sum += arr[i]; // 배열 원소를 순차적으로 더함
}
위 코드의 경우, sum 변수를 생성한후, 반복문을 통해서 0부터 999까지의 수를 sum에 누적시키는 코드입니다.
여기서 sum과 i가 반복문을 거치면서 반복 사용됨에 따라 시간적 지역성에 의거, 캐시에 저장이 되며,
arr[0], arr[1], arr[2]와 같이 순차적으로 인접한 주소를 참조한다는 점에 의거, 캐시는 arr[0] 주변의 데이터를 가져오면서, 효율성을 높입니다.
for(int i = 0; i < 1000; i += 64) {
sum += arr[i]; // 캐시 라인 크기만큼 건너뛰며 접근
}
하지만 위 코드와 같이 반복문에서 건너뛰기 식의 접근을 하게 되면 어떻게 될까요? 이는 오히려 각 순회마다 새로운 캐시라인을 로드하게 되면서, 캐시 공간을 낭비하게 됩니다. 때문에 데이터 구조 및 메모리 접근 패턴을 고려할때, 공간적 지역성에 저해되지 않는 방향으로의 설계하는 것이 성능 최적화와 직결됩니다.
캐시는 보통 L1, L2, L3 등 여러 레벨로 구성되며, CPU의 설계에 따라 소폭 상이하지만, 주로 CPU 내부에 위치하게 됩니다. 여기서 CPU에 가까울수록 속도는 빠르지만 용량은 작아집니다.
CPU 아키텍처
주요 CPU 아키텍처는 ARM과 x86이 있습니다.
ARM 아키텍처: RISC(Reduced Instruction Set Computing)를 기반으로 합니다. RISC란 '감소된 명령어 집합 컴퓨팅'의 약자로, 한 번에 하나의 간단한 동작만을 수행하는 명령어를 사용하는 것을 목표로 합니다. 실제로 ARM 아키텍처에서 사용되는 명령어들은 명령어 길이는 모두 32비트 혹은 64비트와 같이 일정하며, 기본 산술 연산, 메모리 접근, 분기 명령과 같은 작은 범위만을 처리하는 명령어로만 구성됩니다. 이러한 점들로 인해서, x86 아키텍처 대비, 전력 효율성이 높고, 모바일 기기에 주로 사용된다는 특징이 있습니다.
x86 아키텍처:
x86 아키텍처의 경우, CISC(Complex Instruction Set Computing)를 기반으로 합니다. 이는 복잡한 명령어 집합 컴퓨팅의 약자로 ARM에서는 분리되었던 메모리 주소 계산, 메모리 접근, 산술 연산 그리고 결과 저장까지 한 명령어로 수행할 수 있다는 점에서 각 명령어가 담당하는 동작의 범위가 넓습니다.
설령 배열의 합을 구하는 아래 코드를 보면, ARM에선 분리하였던 메모리 접근, 산술 연산, 저장 명령어를 한줄로 수행할 수 있음을 보여줍니다.
// array[i] += 5 를 수행할 경우
// RISC
LDR R1, [R2] // 메모리에서 값 로드
ADD R1, R1, #5 // 더하기 연산 수행
STR R1, [R2] // 결과를 메모리에 저장
// CISC
ADD DWORD PTR [EAX], 5 // 메모리 값에 직접 5를 더함
때문에 모든 명령어가 균일 시간이 소요되고, 고정된 길이를 갖고 있다는 점으로 인해 모든 명령어가 한 사이클에 실행 완료되는 ARM에 비해 x86은 명령어에 따라 여러 사이클이 소요되기도 합니다. 또한, 강력한 연산능력을 바탕으로 전력 효율성보다는 성능을 우선시하여 설계가 된 만큼, 데스크톱, 노트북, 서버와 같은 고성능 컴퓨팅 분야에서 주로 사용됩니다.
하지만, 애플이 ARM 아키텍처를 적용한 실리콘 칩을 개발한 것과 같이 최근에는 두 아키텍쳐의 장점을 혼합하여 발전되는 경향을 보이고 있습니다.
iOS AP와 SoC
Application Processor (AP)
AP는 모바일 기기의 '두뇌' 역할을 하는 핵심 프로세서입니다. 애플의 경우 직접 설계한 A시리즈와 M시리즈 칩을 AP로 사용합니다.
일반 컴퓨터의 CPU와 유사한 역할을 하지만, 모바일 환경에 최적화되어 있다는 차이가 있습니다
저전력 설계
배터리 사용 기기에 최적화
동적 전압/주파수 조절 지원
사용하지 않는 부분의 전원 차단 기능
통합된 기능
CPU 코어
GPU(Graphics Processing Unit)
NPU(Neural Processing Unit)
ISP(Image Signal Processor)
DSP(Digital Signal Processor)
모바일 최적화
터치스크린 인터페이스 지원
모바일 그래픽 가속
카메라 처리 기능
멀티미디어 코덱
SoC(System on a Chip)
SoC는 하나의 칩에 컴퓨터나 전자기기에 필요한 대부분의 구성요소를 통합한 집적회로입니다.
여기서 AP는 SoC의 한 형태로 볼 수 있습니다.
- CPU
- GPU
- Neural Engine
- 메모리 컨트롤러
- 무선 통신 모듈
- 기타 주변장치 컨트롤러
이를 통해 공간 효율성과 전력 효율성을 극대화합니다.
운영체제와 iOS 구조
운영체제는 컴퓨팅 기기, 예를 들어 아이폰과 같은 하드웨어를 효율적으로 사용할 수 있게 해주는 핵심 소프트웨어입니다. 애플의 경우 iOS와 MacOS 등이 있죠.
iOS는 계층화된 아키텍처를 가진 운영체제이며,XNU라는 커널을 기반으로 개발되었습니다.
여기서 XNU 커널은 (X is not unix)의 약자로 유닉스 계열 운영체제인 BSD와 Mach의 장점을 결합해 개발된 커널입니다.
- BSD(Berkely Software Distrubution): 모든 시스템 서비스가 하나의 커널 공간에서 실행되는 모놀리식 커널 구조로 높은 성능 구사
- Mach: 최소한의 핵심기능만 커널에 포함시킨 후, 대부분의 서비스는 User Space에서 실행시키는 마이크로 커널 구조로 설계되었음.
여기서 XNU 커널은 BSD의 모놀리식 구조가 제공하는 높은 성능, 그리고 Mach의 마이크로 커널이 제공하는 모듈화된 구조의 유연성을 모두 가져올 수 있게 되었습니다.
그 XNU 커널 위로는 아래와 같은 계층들이 존재합니다.
- Cocoa Touch 계층: 최상위 계층으로 사용자 인터페이스와 직접적인 상호작용 담당, 기본 앱 인프라 제공,
ex. UIKit, 터치 처리, 푸시 알림 등 - Media 계층: 그래픽, 오디오, 비디오 처리 담당
ex. Core Graphics, Core Audio, Metal 등 - Core Services 계층: 기본적인 시스템 서비스들
ex. Foundation Framework, 네트워킹, 데이터 관리, 파일 시스템 접근 등 - Core OS 계층: 저수준의 기능들 + XNU커널
ex. 보안, 인증서, 하드웨어 통신 담당
여기서 상위 계층은 하위 계층의 서비스를 사용합니다.
계층이 더 낮을 수록 더 기본적이고 필수적인 서비스를 제공하며, 계층이 높을수록 더 추상화되고 사용하기 쉬운 인터페이스를 제공하죠.
샌드박스
iOS의 샌드박스는 어린이들이 모래놀이터에서 자신만의 공간에서 놀듯이, 각각의 앱이 독립된 공간에서 실행되는 구조입니다. 때문에, 시스템 리소스 예를 들어, 사용자의 갤러리,연락처와 같은 데이터와 Bluetooth, Wi-fi와 같은 시스템 설정의 접근이 제한되죠. 이러한 시스템 리소스를 접근하기 위해서는 Info.plist를 통해 적절한 권한 설명을 추가해, 사용자로부터 명시적인 권한을 획득하는 과정이 동반되어야 합니다. 이는 보안과 안정성을 위한 것으로, 악성 앱이 다른 앱의 데이터를 탈취하는 것을 방지합니다.
커널
커널은 운영체제의 '두뇌' 역할을 합니다. 컴퓨터의 CPU, 메모리, 저장장치 등의 하드웨어 자원을 관리하고, 앱들이 이러한 자원을 사용할 수 있게 프로세스 스케줄링을 해줍니다.
- 여러분이 사진 앱에서 사진을 찍으면, 커널이 카메라 하드웨어를 제어하고 촬영된 데이터를 메모리에 저장합니다.
- 음악을 들을 때는 커널이 오디오 하드웨어를 제어하여 소리를 재생합니다.
- 앱을 실행할 때는 커널이 필요한 메모리를 할당하고 관리합니다.
이 밖에도, 메모리 관리, 파일 시스템 관리 등을 담당하고 있습니다.
다중 태스킹(Multitasking)
OS의 다중 태스킹은 여러 앱을 동시에 사용할 수 있게 해주는 기능입니다. 하지만 배터리 수명과 성능을 고려하여 제한적으로 동작합니다. 예를 들어:
- 스포티파이에서 음악을 들으면서 카카오톡으로 채팅할 수 있습니다.
- 네비게이션 앱이 백그라운드에서 위치 추적을 계속할 수 있습니다.
- 하지만 일반 앱들은 백그라운드로 가면 잠시 후 중지되어 배터리를 절약합니다.
프로세스와 스레드
프로세스: 독립된 실행 단위, 독립된 메모리 공간
스레드: 프로세스 내부의 실행 흐름, 메모리 공유
이는 식당에 비유하면 이해하기 쉽습니다:
- 프로세스: 하나의 식당. 각 식당은 자신만의 주방, 재료, 메뉴(즉, 메모리와 자원)를 가지고 있습니다.
- 스레드: 그 식당에서 일하는 요리사들. 한 식당의 요리사들은 같은 주방과 재료를 공유하면서 각자 다른 요리를 만들 수 있습니다.
프로세스
├── 메모리 영역
│ ├── Code Section (공유)
│ ├── Data Section (공유)
│ └── Heap (공유)
│
├── 스레드 1
│ └── Stack (독립)
├── 스레드 2
│ └── Stack (독립)
└── 스레드 3
└── Stack (독립)
각 스레드는 다음 명령어의 위치를 명시하는 Program Counter, 스레드의 현재 작업 상태를 나타내는 Register Set, 스레드 고유의 작업공간인 Stack으로 구성되어있으며, 프로세스 단에 위치한 하드웨어 컨텍스트를 공유받습니다. 여기서 하드웨어 컨텍스트란 코드영역, 데이터 영역 그리고 동적 메모리 할당영역인 Heap 영역이 있습니다.
멀티스레딩:
멀티스레딩은 하나의 프로세스 내에서 여러 실행 흐름(스레드)이 동시에 실행되는 것을 의미하며, 이전에 말씀드렸던 동시성 프로그래밍에 포함되는 개념입니다. 다만 동시성 프로그래밍이 Task를 단위로 한다면, 멀티스레딩은 스레드를 단위로 하죠. 또한, 자원공유가 추상화된 동시성 프로그래밍에 비해 각 스레드는 프로세스의 리소스를 공유하면서 독립적으로 실행됩니다.
멀티스레딩이 필요한 이유:
병렬 처리로 인해, 리소스를 효율적으로 사용할 수 있게 되기 때문입니다. 예를 들어 UI처리와 백그라운드 작업이 분리되지 않을 경우, UI Freezing 현상이 생길 수 있지만, 멀티스레딩을 통해 각 작업을 분리하게 되면, 사용자로부터의 인터렉션을 딜레이없이 대응할 수 있게 됩니다.
이를 다시 식당의 예시로 설명하자면, 요리사(스레드)가 오래 걸리는 요리를 하는 상황을 생각하면 됩니다. 만일 요리사가 한명일 경우, 즉 단일 스레드로 프로세스가 구성될 경우, 요리 도중 다른 주문을 받지 못하기에 손님들이 오래 기다려야 하겠죠? 여러 요리사(멀티스레드)가 있다면:
- 한 요리사가 메인 요리를 만드는 동안 다른 요리사가 전채 요리를 준비할 수 있습니다.
- 따라서 주방이 더 효율적으로 운영되며, 손님들의 대기 시간이 줄어듭니다.
GCD(Grand Central Dispatch)
동시성 프로그래밍을 위한 Apple의 프레임워크입니다.
DispatchQueue를 기반으로 하며, 복잡한 스레드 관리를 추상화하여 개발자가 '무슨 작업을 할지'에만 집중할 수 있게 해주는 것이 가장 큰 특징입니다. 주요 특징들을 하나씩 설명해드리겠습니다.
작업 큐 기반 동시성 지원
작업을 큐(Queue)에 넣으면 시스템이 알아서 적절한 스레드에서 실행해주는 방식입니다. 크게 Serial Queue와 Concurrent Queue를 제공합니다.
여기서 Serial Queue란 한 번에 하나의 작업만 실행하며, 작업들이 추가된 순서대로 순차적으로 실행되는 반면, Concurrent Queue는 동시에 여러 작업을 실행할 수 있으며, 시스템이 허용하는 만큼의 병렬 처리가 가능하다는 작업 실행 방식의 차이가 있습니다.
아래 코드는 GCD 프레임워크를 활용해 이미지 다운로드 작업을 Concurrent Queue에 추가하는 코드입니다!
class ImageLoader {
let queue = DispatchQueue(label: "com.example.imageloader", attributes: .concurrent)
func loadImage(url: URL, completion: @escaping (UIImage?) -> Void) {
queue.async {
// 이미지 다운로드 작업
let data = try? Data(contentsOf: url)
let image = data.flatMap { UIImage(data: $0) }
DispatchQueue.main.async {
completion(image) // UI 업데이트는 메인 큐에서
}
}
}
}
또한 시스템 수준에서 스레드 풀을 관리하기에 직접 스레드를 생성하고 관리하는 로직을 자동으로 처리해줍니다.
감사합니다.
'개발지식 정리 > CS정리' 카테고리의 다른 글
iOS에서의 동시성 프로그래밍 (0) | 2025.01.23 |
---|---|
iOS CS 정리(241219) (0) | 2024.12.12 |
CS 정리 (10월 30일) (0) | 2024.10.30 |