iOS/NumberterKit

[NumberterKit] 성능 테스트 - 락 기법별 상세 분석

Dev_Ted 2025. 7. 3. 15:31

배경

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