배경
NumberFormatter는 생성 비용이 크고 내부 상태를 가지기 때문에, 멀티스레드 환경에서 공유할 경우 성능 최적화와 함께 스레드 안정성도 중요한 고려사항입니다.
이번 실험에서는 os_unfair_lock, Mutex(NSLock), RecursiveLock, SpinLock 등 여러 락 기법을 사용해 NumberFormatter를 공유했을 때의 실행 시간, CPU 효율, 메모리 사용량을 측정하여 가장 효율적인 방법을 분석하였습니다.
성능 측정 지표 정의
Clock Time | 실제 경과 시간 (사용자가 체감하는 대기 시간) |
CPU Time | 모든 스레드의 CPU 연산 총합 |
CPU 활용도 | CPU Time ÷ Clock Time × 100 (멀티코어 활용도 지표) |
Memory Peak | 프로그램 실행 중 가장 많이 사용된 메모리 |
성능 향상 배수 | 기준 방식(싱글 스레드 생성) 대비 얼마나 빨라졌는지 |
락 기법별 성능 비교 결과
락 종류 | Clock Time (초) | CPU Time (초) | CPU 활용도 (%) | Memory Peak (KB) | 성능 향상 (배수) |
os_unfair_lock | 1.071초 | 1.264초 | 118.1% | 15,222KB | 3.85x |
Mutex | 1.263초 | 1.473초 | 116.7% | 15,309KB | 3.27x |
RecursiveLock | 1.203초 | 1.476초 | 122.8% | 15,302KB | 3.59x |
SpinLock | 2.014초 | 8.426초 | 413.4% | 15,337KB | 2.60x |
테스트 결과 분석
테스트를 통해 다음과 같은 사실들을 알 수 있었습니다.
os_unfair_lock이 가장 우수한 성능
- 가장 빠른 Clock Time (1.071초)
- 가장 낮은 CPU Time (1.264초)
- 가장 적은 메모리 사용량 (15,222KB)
- 락 기법 중 최고 성능 (3.85배 향상)
이는 내부적으로 우선순위 역전 방지, 빠른 경합 해소, 낮은 오버헤드를 목표로 설계되었기 때문입니다.
SpinLock의 CPU 낭비 문제
- Busy waiting 구조로 락을 획득할 때 CPU를 계속 소비
- Clock Time은 2.014초로 느리고, CPU Time은 무려 8.4초로 과다
- CPU 활용도 413.4% → 멀티코어를 쓰긴 했으나 매우 비효율적
→ 실험 결과, 락 대기 중 과도한 CPU 리소스 낭비가 병목 요인으로 작용했습니다.
스레드 안전한 NumberFormatter 공유 구조 설계
멀티스레드 환경에서 NumberFormatter를 공유할 경우 Race Condition이 발생할 수 있으므로 동기화가 필요합니다. 각 락 기법의 특징은 다음과 같습니다:
os_unfair_lock | Apple에서 설계한 저수준 락, 빠른 경합 해소, 낮은 오버헤드 |
NSLock(Mutex) | 일반적인 락, 경합 시 스레드 sleep으로 컨텍스트 스위칭 비용 발생 |
RecursiveLock | 동일 스레드의 재귀 진입 허용, 메타데이터 관리 오버헤드 존재 |
SpinLock | 락 획득 시까지 무한 루프, CPU 자원 과소비 발생 |
결론적으로, os_unfair_lock은 처리 속도, CPU 자원 효율, 메모리 측면에서 가장 우수한 선택지로 나타났습니다.
os_unfair_lock의 한계와 대안
비록 os_unfair_lock이 처리 속도, CPU 자원 효율, 메모리 사용량 측면에서 가장 우수한 결과를 보여주었지만, 실제 적용 시 유의해야 할 한계점도 존재합니다.
1. 재귀적 잠금(Recursive Locking)을 지원하지 않음
- os_unfair_lock은 동일한 스레드에서 중첩적으로 lock을 시도할 경우 데드락이 발생합니다.
- 즉, 어떤 함수 내부에서 다시 동일한 락을 사용하는 함수를 호출하는 구조에서는 사용할 수 없습니다.
2. iOS 10 / macOS 10.12 이상에서만 사용 가능
- Apple이 os_unfair_lock을 도입한 것은 비교적 최근 버전부터이며,
- 그 이전 버전에서는 사용할 수 없고, 컴파일 자체도 되지 않습니다.
- 따라서 구형 OS까지 지원하는 앱이나 SDK 개발 시에는 NSLock 등을 사용해야 합니다.
언제 어떤 락을 선택해야 할까?
이러한 결과를 기반으로 제가 생각하는 각 상황별 적용해야 하는 락은 다음과 같습니다.
단순한 고성능 동기화 | os_unfair_lock (최고 성능) |
재귀 호출 구조가 있음 | NSRecursiveLock |
락 소유 여부 확인 필요 | NSLock, NSRecursiveLock, GCD serial queue |
구형 OS까지 지원 필요 | NSLock, DispatchQueue |
결론적으로
- 단일 객체 공유 + 단순한 임계 구간 보호에는 os_unfair_lock이 가장 뛰어납니다.
- 하지만 재귀 호출, 조건부 락, 구형 OS 호환성이 필요한 경우에는 다른 락 기법을 사용해야 합니다.
- 성능 외에도 코드 구조와 요구 조건을 종합적으로 고려한 락 선택이 중요합니다.
728x90
'iOS > NumberterKit' 카테고리의 다른 글
[NumberterKit] 성능 테스트 - 스레드 수별 상세 분석 (0) | 2025.06.15 |
---|---|
[NumberterKit] 성능 테스트 결과 (0) | 2025.05.16 |
[NumberterKit] Formatter 최적화: SpinLock 적용기 (0) | 2025.05.09 |
[NumberterKit] NumberterKit을 만들게 된 계기와 컨셉 (0) | 2025.04.20 |