← 모든 글

앱 응답성을 위한 Instruments 27의 새로운 기능

WWDC 2026에서 두 명의 Apple 엔지니어가 세 가지 별개의 멈춤 현상을 보이는 메모 앱을 프로파일링했습니다. 저장 시 펜슬이 응답하지 않는 현상, 스크롤이 끊기는 현상, 올가미 도구를 사용할 때 멈추는 현상이었습니다. 그들은 각 증상에 Instruments 27의 새 도구를 하나씩 대응시켜 세 가지를 모두 수정했고, 베이스라인과 최적화 버전을 비교해 각 수정을 입증했습니다.1 이 세션의 핵심 주장은 하나의 진단 흐름입니다. 멈춤이 일어나는 동안 CPU를 읽으면, CPU가 다음에 어떤 인스트루먼트를 열어야 할지 알려준다는 것입니다. CPU가 높으면 코드가 너무 느린 것입니다. CPU가 유휴 상태이면 코드가 차단된 것입니다. Instruments 27은 이 두 경우를 모두 해결할 도구와, 수정이 실제로 효과가 있었는지 확인하는 새로운 뷰를 제공합니다.

이 글에서는 그 흐름의 축이 되는 세 가지 도구를 살펴봅니다. CPU가 포화 상태일 때 self weight가 큰 지점을 찾는 Top Functions, 작업이 Main Actor를 두고 경쟁할 때 어떤 executor에서 작업이 실행되었는지 보는 새로운 Swift executors 인스트루먼트, 그리고 스레드가 시스템을 기다리며 유휴 상태일 때 syscall 인수를 읽는 새로운 Inspector 패널입니다. Run Comparisons는 각 변경이 트레이스를 개선했는지 측정함으로써 이 도구들을 하나로 묶습니다. 아래 내용은 모두 세션에서 직접 가져온 것입니다.

TL;DR

  • Instruments 27은 응답성 워크플로를 하나의 진단 규칙을 중심으로 재구성했습니다. 먼저 Time Profiler를 열고, 멈춤이 일어나는 동안 메인 스레드 CPU를 확인하면, 그 측정값이 적절한 도구로 안내합니다.1
  • Top Functions는 새로운 분석 모드로, 콜 계층을 버리고 흩어진 모든 노드를 self weight 기준으로 병합합니다. 플레임 그래프가 여러 분기에 걸쳐 쪼개버리는 런타임 오버헤드를 드러냅니다.1
  • Run Comparisons는 “New in Instruments”이며, 베이스라인 트레이스와 최적화된 트레이스 사이의 정확한 성능 차이를 계산합니다. 실행 간에 각 함수를 대응시키고, 회귀는 빨간색, 개선은 초록색으로 표시합니다.1
  • 새로운 Swift executors 인스트루먼트는 Main Actor, 전역 동시성 executor, 그리고 모든 커스텀 executor를 시각화합니다. 덕분에 작업이 어떤 executor에서 실행되었는지 확인하고 Main Actor 경합을 잡아낼 수 있습니다.1
  • 새로운 Inspector 패널은 시스템 콜의 정확한 인수(파일 디스크립터, 버퍼 주소, 쓰기 크기)를 보여주고 on-core 시간과 off-core 시간을 분리합니다. 이를 통해 메인 스레드에서의 1.7 GB 쓰기 같은 동기적 차단이 드러납니다.1

Instruments 27이 중심으로 삼는 진단 흐름

새 도구를 다루기 전에, 세션은 이 도구들을 묶는 규칙을 먼저 제시합니다. 앱이 프레임을 떨어뜨리거나 멈출 때 첫 단계는 Time Profiler이며, 이는 상황을 파악할 수 있는 상위 수준의 개요를 제공합니다.1 거기서부터 모든 것을 분류하는 질문은 단 하나입니다. 멈춤이 일어나는 동안 CPU는 무엇을 하고 있는가입니다.

CPU 사용률이 높으면 스레드는 바쁘고 작업에 시간이 너무 오래 걸리고 있으며, 이는 코드 성능 병목을 가리킵니다. 이를 고치는 방법은 두 가지입니다. 알고리즘을 더 빠르게 동작하도록 리팩터링하거나, 무거운 작업이 불가피한 경우 백그라운드 작업으로 오프로드해 인터페이스의 응답성을 유지하는 것입니다.1 반면 프로세서가 유휴 상태인데 앱이 멈춘다면, 알고리즘 최적화는 도움이 되지 않습니다. 메인 스레드가 리소스가 해제되기를 기다리며 막혀 있기 때문입니다. 파일 I/O, 동기화 락, 프로세스 간 통신 중 하나입니다. 세션은 이렇게 말합니다. “Time Profiler는 활성 CPU 사이클만 모니터링하기 때문에, 이러한 이벤트는 전혀 보여주지 못합니다.”1

엔지니어들은 메모 앱의 릴리스 빌드를 프로파일링했습니다. “디버그 빌드는 디버그 용이성을 위해 런타임 성능을 희생하기 때문에, 디버그 빌드의 프로파일링 데이터는 오해를 일으킬 수 있기” 때문입니다.1 그들은 Swift Concurrency 템플릿을 선택했는데, 이 템플릿에서도 Time Profiler 인스트루먼트를 사용할 수 있으며, 세 가지 멈춤을 모두 하나의 베이스라인 트레이스에 기록했습니다. 또한 OSSignposter 타입을 사용해 올가미 선택을 os_signpost 구간으로 감싸고, 카테고리를 points of interest로 설정해 Instruments가 그 구간을 points of interest 트랙에 표시하도록 했습니다. 이 signpost는 트레이스를 필터링하는 기준점이 되고, 나중에는 노이즈 없는 run comparison의 기준점이 됩니다.1

Watch on Apple Developer ↗

Art와 Harjas가 데모에 앞서 1:50부터 진단 흐름을 설명합니다.

Instruments 27 창 자체가 워크플로를 짜줍니다. 상단의 타임라인에는 작업, 액터, executor에 대한 가로 트랙이 표시됩니다. 그 아래의 세부 영역은 선택한 트랙에 따라 바뀝니다. 오른쪽에는 “완전히 새로운 Inspector 패널”이 있어, 선택한 내용에 따라 추가 세부 정보와 동작을 보여줍니다.1 세 가지 도구가 이 틀을 채우며, 각각은 세 가지 멈춤 중 하나에 연결됩니다.

Top Functions: CPU가 포화 상태일 때

올가미 멈춤은 높은 CPU로 나타납니다. 트레이스를 올가미 os_signpost 구간으로 필터링하자, hangs 인스트루먼트가 거기서 여러 멈춤을 확인했고, 프로세스 트랙을 메인 스레드까지 펼치자 CPU가 “이 기간 동안 100% 부근에 머물러 있는” 것이 보였습니다.1 CPU가 높다는 것은 코드가 실행되고 있지만 시간이 너무 오래 걸린다는 뜻이므로, Time Profiler가 적절한 도구입니다.

세션은 여기서 플레임 그래프만으로는 왜 충분하지 않은지 설명합니다. Time Profiler는 하드웨어 타이머를 사용해 기본적으로 1밀리초마다 콜 스택을 샘플링하고, 각 코어의 현재 스택을 기록합니다. 샘플링된 각 함수에는 가중치가 부여되며, 스택 맨 아래의 함수에는 self weight, 즉 그 함수 내부에서 명령을 직접 실행하는 데 쓴 시간이 부여됩니다.1 플레임 그래프는 그 콜 트리를 공간적 블록으로 그려내는데, 호출자는 위에, 호출되는 함수는 아래로 뻗어나가고, 막대 너비는 총 CPU 시간에 비례합니다.1 문제는 이렇습니다. “Swift 런타임 함수와 다양한 헬퍼 유틸리티처럼 여러 곳에서 호출되는 코드”의 경우, 플레임 그래프는 총비용을 그것을 호출하는 모든 분기에 분산시킵니다. 실행 시간이 작은 조각들로 쪼개지면서 “어떤 특정 함수가 전체적으로 가장 많은 사이클을 소모했는지 답하기 어려워집니다.”1

바로 그 공백을 Top Functions가 메웁니다. 세션이 새 모드를 설명하듯이, “이 새로운 모드는 콜 계층을 버립니다. 대신 흩어진 모든 노드를 추출해 하나의 블록으로 합칩니다.” 이는 self 메트릭으로 평가됩니다.1 올가미 플레임 그래프를 살펴봐도 단일한 주범은 없었고, 그저 “서로 다른 코드 경로가 합쳐져 멈춤을 일으킬 만큼 비용이 큰” 것뿐이었습니다.1 self weight로 정렬한 Top Functions로 전환하자, 최상위 항목은 swift_project_boxed_opaque_existential이었습니다. 이는 코드가 existential을 다룰 수 있도록 그것을 언래핑하는 런타임 함수입니다.1

수정은 타임라인이 아니라 타입 시스템 안에 있었습니다. 엔지니어는 Xcode 코딩 어시스턴트에게 드로잉 코드를 existential 대신 구체 타입과 제네릭을 사용하도록 다시 작성해 달라고 요청했습니다. existential은 크기가 가변적이고 접근에 추가 작업이 필요해, 이 사용 사례에는 비용이 너무 컸기 때문입니다.1 이 도구가 주는 교훈은 이렇습니다. Top Functions는 플레임 그래프의 어떤 단일 분기로도 드러나지 않는, 흩어진 소프트웨어 오버헤드를 잡아내기 위해 존재합니다.

Run Comparisons: 수정이 효과가 있었음을 증명하기

예전에는 수정을 확인하려면 두 트레이스를 별도의 창에서 열고 Top Functions 데이터를 나란히 눈으로 비교해야 했습니다. Instruments 27은 이를 Run Comparisons로 대체하며, 세션에서는 이를 “New in Instruments”라고 설명합니다.1 이는 “베이스라인 트레이스와 최적화 트레이스의 모든 샘플을 상호 참조해 정확한 성능 차이를 계산”하며, 스택의 모든 노드를 평가합니다. 베이스라인 실행의 함수 옛 버전을 최적화 실행의 새 버전에 대응시키고, 차이를 계산해 성능 차이로 정렬합니다. 빨간 블록은 회귀를, 초록 블록은 개선을 나타냅니다.1

이 워크플로는 노이즈 제거에 매우 신경 씁니다. 엔지니어들은 먼저 두 실행을 올가미 선택의 정확히 동일한 os_signpost 구간으로 필터링한 뒤, 메인 스레드 트랙을 선택하고 비교 버튼을 클릭해 드롭다운에서 베이스라인 실행을 골랐습니다.1 이로써 사이드바에 비교 탭이 추가되며, “여러 비교를 만들 수 있고, 이들은 문서에 저장되어 협업을 더 쉽게 만듭니다.”1

비교는 솔직한 이야기를 들려주었습니다. 텍스트 콜 트리는 전체 올가미 실행 시간이 줄었음을 보여주었습니다. 플레임 그래프는 개선된 경로를 초록색으로, 회귀한 경로를 빨간색으로 보여주었고, 그 회귀는 “코딩 어시스턴트가 existential 사용을 제거하려고 작업하면서 추가한 새로운 함수들”이었습니다.1 Top Functions 뷰에서는 회귀가 기본적으로 최상위로 정렬됩니다. 정렬을 뒤집자 swift_project_boxed_opaque_existential이 “완전히 제거되었음”이 드러났고, “전반적으로 개선이 회귀를 능가(out[weigh])한다”는 것이 분명해졌습니다.1 이 차이가 바로 검증 단계입니다. Run Comparisons는 수정을 희망이 아니라 측정된 결과로 만들기 위해 존재합니다. 세션이 일관되게 권하는 조언은 “run comparison의 구간을 신뢰할 수 있게 하기 위해 os_signpost를 활용하라”는 것입니다.1

Swift executors 인스트루먼트: 작업이 Main Actor를 두고 경쟁할 때

스크롤 멈춤에는 기댈 만한 points-of-interest 로그가 없었습니다. 세션은 다른 종류의 맥락에 손을 뻗습니다. “이러한 멈춤이 일어나는 동안 Main Actor 위에 어떤 작업이 있는가”입니다.1 그것이 바로 새로운 Swift executors 인스트루먼트의 역할이며, 이는 “프로세스 내의 Main Actor, 전역 동시성 executor, 그리고 모든 커스텀 executor를 시각화”합니다.1

각 스크롤 멈춤마다 Main Actor 트랙에는 renderThumbnail이라는 이름의 Swift 작업이 표시되었습니다. 트랙을 선택하자 Main Actor 위의 작업들이 요약되며 “Main Actor 위에서 실행에 수백 ms가 걸리는 여러 render thumbnail 작업”이 드러났고, 이는 끊기는 스크롤과 일치합니다.1 진단 흐름을 따라 엔지니어는 하나의 멈춤으로 필터링하고 Inspector를 사용해 메인 스레드를 고정했습니다. Time Profiler는 CPU가 “100% 부근”이라고 보고해, 시스템 리소스 대기 가능성을 배제했습니다. 작업들은 단지 Main Actor 위에서 시간이 너무 오래 걸리고 있었을 뿐입니다.1

근본 원인은 이 인스트루먼트가 보이게 해주는 컨텍스트 상속의 미묘한 지점입니다. Main Actor는 모든 UI 업데이트와 상호작용을 처리합니다. 앱은 썸네일을 비동기로 렌더링했지만, 그 코드가 SwiftUI에서 호출되었기 때문에 “Main Actor 컨텍스트를 상속받았고”, 그래서 썸네일 작업이 중요한 UI 업데이트와 경쟁했습니다.1 수정은 작업을 스레드 풀로 보냅니다. 엔지니어는 작업 이니셜라이저에 @concurrent 속성을 추가했고, 이로써 렌더링 작업이 Main Actor에서 전역 executor로 옮겨지며, Swift 컴파일러는 이 변경이 경쟁 조건을 일으키지 않음을 확인합니다.1 인스트루먼트가 그 이동을 입증했습니다. 업데이트된 트레이스에서 “썸네일 렌더링 작업이 Main Actor 트랙에서 전역 executor 트랙으로 이동”했고, 이제 작업이 병렬로 실행됩니다.1 이 도구의 요점은 이렇습니다. Swift executors 인스트루먼트는 어떤 executor가 어떤 작업을 실행했는지 정확히 보여줌으로써 액터 혼잡을 식별하기 위해 존재합니다.

Inspector 패널: 스레드가 유휴 상태이며 차단되었을 때

저장 멈춤은 앞의 두 가지와 반대입니다. Write to File os_signpost 구간으로 필터링하고 확대하자, 보고된 마이크로 멈춤은 CPU가 “20% 부근에서 맴돌고 있음”을 보여주었습니다.1 낮은 CPU는 오해를 일으킵니다. “그것은 코드가 느리게 실행된다는 뜻이 아니라, 스레드가 실행을 멈췄다는 뜻입니다.”1 낮은 CPU 상태에서 인터페이스가 멈출 때, 메인 스레드는 시스템 리소스를 기다리며 차단되어 있으며, 알고리즘을 최적화해도 아무것도 얻지 못합니다. “최적화할 코드가 실행되고 있지 않기” 때문입니다.1

그 증상에 대해 세션은 System Trace 템플릿으로 전환합니다. 이는 “운영체제가 언제, 왜 애플리케이션을 일시 중단하는지를 정확히 시각화하기 위해 만들어진” 것입니다.1 세션은 스레드 상태 모델을 짚어갑니다. 사용할 수 없는 리소스에 도달한 실행 중인 스레드는 차단 상태에 들어가고, 커널이 그것을 프로세서에서 내보내며, 리소스가 준비되었을 때만 실행 가능(runnable) 상태가 되어 스케줄러가 빈 코어를 할당하기를 기다립니다. 요청의 다음 단계를 조율하기 위한 이 짧은 깨어남이 바로 “그 20%의 CPU 사용률을 일으키는 것”입니다.1

System Trace에서 저장 작업의 활동 레인에는 “많은 양의 빈 공간”이 표시되어 스레드가 차단되었음을 가리켰고, 보라색 구간이 실행 중인 시스템 콜을 표시했습니다.1 한 구간을 선택하자 클릭한 세그먼트보다 더 많은 부분이 강조되며, “on-core 시간과 off-core 시간에 모두 걸치는 하나의 연속된 write 시스템 콜”이 시각화되었습니다. 불투명한 세그먼트는 on-core 실행이고, 반투명한 세그먼트는 off-core 차단입니다.1

새로운 Inspector 패널은 그것을 판정으로 바꿉니다. 세션이 말하듯이, “Inspector는 이 시스템 콜에 전달된 정확한 인수를 보여줍니다. 대상 파일 디스크립터, 버퍼의 메모리 주소, 그리고 무엇보다 크기를 볼 수 있습니다.”1 크기가 결정적 증거였습니다. 앱은 “메인 스레드에서 1.7기가바이트가 넘는 데이터를 쓰려고 하고” 있었습니다.1 Inspector는 비용도 보여주었습니다. 이 단일 작업은 “500밀리초가 넘게 걸렸고, 그중 거의 300밀리초는 디스크를 기다리는 off-core에 쓰였습니다.”1 동기적 data.write 호출이 병목이었습니다. 인코딩과 쓰기를 Swift 작업으로 감싸 동시성 스레드 풀로 밀어내자, 검증 트레이스는 write 시스템 콜이 이제 메인 스레드가 아니라 백그라운드 스레드에 나타남을 확인했습니다.1 Inspector 패널은 CPU만 보는 프로파일러로는 볼 수 없는, 파일 I/O 같은 동기적 차단 동작을 드러내기 위해 존재합니다.

핵심 요점

iOS 및 macOS 엔지니어를 위해:

  • 먼저 Time Profiler를 열고 멈춤이 일어나는 동안 메인 스레드 CPU를 읽으세요. 높은 CPU는 Top Functions로, 유휴 CPU는 System Trace와 Inspector로 안내합니다.1
  • 플레임 그래프가 단일한 주범을 보여주지 않을 때 Top Functions를 사용하세요. 흩어진 런타임 오버헤드를 self weight 기준으로 병합해 가장 비용이 큰 함수가 드러나게 합니다.1

성능 수정을 출시하는 팀을 위해:

  • 모든 변경을 동일한 os_signpost 구간으로 필터링한 Run Comparisons로 검증하고, 나란히 눈으로 비교하는 것에 의존하는 대신 빨강/초록 차이를 읽으세요.1
  • 비교는 문서에 저장되고 여러 실행을 쌓을 수 있어, 수정의 증거가 검토를 위해 트레이스와 함께 따라다닙니다.1

동시성 및 응답성 작업을 위해:

  • 작업이 Main Actor를 두고 경쟁할 때 Swift executors 인스트루먼트에 손을 뻗으세요. 작업이 Main Actor, 전역 동시성 executor, 커스텀 executor 중 어디에서 실행되었는지 보여줍니다.1
  • 항상 릴리스 빌드를 프로파일링하세요. 디버그 빌드는 오해를 일으키는 데이터를 만들기 때문입니다.1

FAQ

Instruments 27의 Top Functions 모드란 무엇인가요?

Top Functions는 Time Profiler의 새로운 분석 모드입니다. 콜 계층을 버리고 함수의 흩어진 모든 인스턴스를 하나의 블록으로 합쳐, self weight, 즉 그 함수 내부에서 명령을 직접 실행하는 데 쓴 시간으로 순위를 매깁니다. 플레임 그래프가 어려워하는 질문에 답합니다. Swift 런타임 함수와 헬퍼 유틸리티처럼 비용이 여러 호출 분기에 걸쳐 쪼개져 있을 때, 어떤 특정 함수가 전체적으로 가장 많은 사이클을 소모했는가라는 질문입니다.1

Instruments의 Run Comparisons는 어떻게 작동하나요?

Run Comparisons는 세션에서 Instruments의 새 기능으로 설명되며, 베이스라인 트레이스와 최적화된 트레이스 사이의 정확한 성능 차이를 계산합니다. 두 실행 간에 각 함수를 대응시키고, 스택의 모든 노드에 대한 차이를 계산해 성능 차이로 정렬하며, 회귀는 빨간색, 개선은 초록색으로 표시합니다. 깔끔한 비교를 위해서는 두 실행을 동일한 os_signpost 구간으로 필터링하고, 트랙을 선택한 뒤, 드롭다운에서 베이스라인을 고릅니다. 비교는 문서에 저장됩니다.1

Swift executors 인스트루먼트는 무엇을 보여주나요?

Instruments 27의 Swift executors 인스트루먼트는 프로세스 내의 Main Actor, 전역 동시성 executor, 그리고 모든 커스텀 executor를 시각화합니다. 특정 Swift 작업이 어떤 executor에서 실행되었는지 볼 수 있게 해주어 Main Actor 경합을 잡아낼 수 있습니다. 세션에서는 SwiftUI에서 호출된 코드가 Main Actor 컨텍스트를 상속받은 탓에 renderThumbnail 작업이 Main Actor 위에 갇혀 있는 것이 드러났고, 이들을 전역 executor로 옮기자 멈춤이 해소되었습니다.1

파일 I/O로 인한 멈춤은 어떻게 찾나요?

인터페이스는 멈춰 있는데 메인 스레드 CPU가 낮을 때(세션에서는 20% 부근), 스레드는 느린 코드를 실행하는 것이 아니라 시스템 리소스를 기다리며 차단되어 있습니다. System Trace 템플릿으로 전환하고 시스템 콜 구간을 선택하세요. 새로운 Inspector 패널이 파일 디스크립터, 버퍼 주소, 쓰기 크기를 포함한 syscall의 정확한 인수와 on-core 대 off-core 시간을 보여줍니다. 데모에서는 메인 스레드에서의 1.7 GB 동기 쓰기가 드러났습니다.1


이 세션이 가르치는 진단 흐름은 응답성의 SwiftUI 측면을 다루는 iOS 27의 SwiftUI 성능 및 상호운용과, 측정 사고방식을 다루는 성능의 사각지대와 짝을 이룹니다. Main Actor 멈춤을 해소한 동시성 이동은 실전에서의 Swift 6.2 동시성에서 다루는 더 넓은 모델 안에 자리합니다. 전체 시리즈 허브는 Apple Ecosystem Series입니다.

참고 문헌


  1. Apple, WWDC 2026 session 268, Profile, fix, and verify: Improve app responsiveness with Instruments. 진단 흐름(먼저 Time Profiler, 메인 스레드 CPU 읽기가 조사를 분류함), 릴리스 빌드 및 Swift Concurrency 템플릿 안내, os_signpost / OSSignposter의 points-of-interest 구간, 새로운 Inspector 패널, 콜 트리와 플레임 그래프의 샘플링 모델(기본 1밀리초 비율, self weight), Top Functions 분석 모드와 swift_project_boxed_opaque_existential 발견, 빨강/초록 차이와 문서에 저장되는 비교 탭을 갖춘 Run Comparisons(“New in Instruments”), Swift executors 인스트루먼트(Main Actor, 전역 동시성 executor, 커스텀 executor)와 @concurrent 속성으로 해소한 renderThumbnail의 Main Actor 경합, 그리고 1.7 GB 동기 쓰기에 대한 System Trace와 Inspector 진단(파일 디스크립터, 버퍼 주소, 쓰기 크기, on-core 대 off-core 시간, 500밀리초 이상 중 거의 300밀리초가 off-core)의 출처. 

관련 게시물

WWDC26 랩에서 Apple 성능 팀이 말한 것

Apple의 Power & Performance 팀이 WWDC26에서 개발자 질문에 실시간으로 답했습니다. MetricKit, Power Profiler, 발열, AI 경합에 관한 현장 지침을 정리합니다.

10 분 소요

MetricKit 재탄생: iOS 27의 상태 인식 텔레메트리

iOS 27에서 MetricKit이 재구축되었습니다. 비동기 메트릭 및 진단 스트림, Codable 리포트, 그리고 메트릭을 앱 상태별로 분리하는 StateReporting 프레임워크를 살펴봅니다.

9 분 소요

76점에서 100점으로: 완벽한 Lighthouse 점수 달성기

개인 포트폴리오 사이트의 모바일 Lighthouse 성능 점수를 76점(CLS 0.493)에서 모든 카테고리 완벽한 100/100/100/100으로 개선한 과정을 소개합니다.

5 분 소요