nyancoder

WWDC 2021 - Detect and diagnose memory issues 본문

WWDC/WWDC 2021

WWDC 2021 - Detect and diagnose memory issues

nyancoder 2021. 7. 6. 02:31

원본 영상: https://developer.apple.com/videos/play/wwdc2021/10180/

 

Impact of memory footprint

  • 앱이 백그라운드에 있을 때 메모리 공간을 적게 사용할수록 앱이 종료되지 않을 가능성이 높습니다.
  • 종료되지 않은 앱으로 다시 진입할 때는 빠르게 재진입할 수 있습니다.
  • 메모리를 적게 사용할수록 애니메이션이나 동영상과 같은 추가적인 기능을 사용할 수 있습니다.
  • 오래된 기기는 메모리를 작게 사용하기 때문에 메모리를 적게 사용해야만 오래된 기기에서도 동작할 수 있습니다.

  • Dirty 메모리는 앱에서 기록한 데이터, malloc의 할당된 메모리, 디코딩된 이미지, 프레임워크에서 사용된 메모리로 구성됩니다.
  • Compressed메모리는 최근에 접근되지 않은 메모리가 압축되어 보관됩니다.
  • 압축된 메모리는 접근 시에 다시 압축 해제됩니다.
  • iOS에는 macOS와 같은 swap 메모리가 존재하지 않습니다.
  • Clean 메모리는 접근되지 않은 데이터나 메모리에 매핑된 파일, 프레임 워크 메모리로 구성됩니다.

Memory footprint라고 하면 사용되고 있는 메모리인 Dirty와 Compressed 메모리의 합을 나타냅니다.

 

Tools for profiling memory

  • Xcode가 제공하는 툴들을 사용하면 개발과 실사용에서 모두 메모리 문제를 측정할 수 있습니다.

  • XCTest 프레임워크는 Unit 테스트나 UI 테스트에서 메모리를 확인할 수 있으며, MetricKit 및 Xcode Organizer는 사용자의 메모리 사용을 확인할 수 있습니다.
  • Performance XCTests를 사용하면 메모리 사용률, CPU 사용량, 디스크 기록 등과 같은 시스템 리소스를 측정할 수 있습니다.

  • measure (metrics : options : block :) API를 통해 메모리 성능 테스트를 할 수 있습니다.
  • startMeasuring() 함수를 이용해서 측정의 시작을 지시합니다.
  • 이후 사용자 UI를 탭을 하는 것 같은 동작을 통해 테스트가 필요한 시나리오를 수행합니다.

  • 실행 결과에서는 5 회 반복의 측정 결과와 평균값이 표시됩니다.
  • Set Baseline 버튼을 통해서 기준치를 설정할 수 있으며, 이후의 실행에서 설정된 기준치보다 사용된 메모리의 평균값이 크면 테스트가 실패합니다.

  • Ktrace 파일을 사용하여 렌더링 파이프 라인이 끊기는 이유를 분석하거나, 메인 스레드에서 중단이 발생하는 원인을 찾은 것과 같은 일을 할 수 있습니다.
  • Ktrace 파일은 Instruments에서 열어서 분석할 수 있습니다.

  • Memory Graph는 각 가상 메모리 영역과, malloc으로 할당된 각 블록의 주소와 크기를 기록합니다.
  • 이를 통해 힙 공간에 할당된 객체가 서로 연결된 구조를 볼 수 있습니다.
  • XCTest는 객체를 할당할 때 malloc 스택 로깅을 자동으로 활성화합니다.

  • enablePerformanceTestsDiagnostics YES 플래그와 함께 xcodebuild commandline 도구를 사용하여 메모리 측정을 할 수 있습니다.
  • 이 플래그는 Ktrace와 memgraph를 수집합니다.
  • 실행 결과가 콘솔에 출력되며 테스트의 통과 유무를 알 수 있고, 메모리가 이전에 설정한 baseline을 넘어서 실패했는지 유무도 알 수 있습니다.
  • xcresult의 경로 또한 출력에서 얻을 수 있습니다.

 

Tools for profiling memory

  • 객체 A가 객체 B를 참조하고 있다가 더 이상 참조하지 않게 되었을 때, 객체 B가 ARC를 사용하지 않는 unsafe계열의 객체인데 명시적으로 메모리를 해지해 주지 않으면 객체 B를 더 이상 참조할 방법이 없어 메모리 릭이 됩니다.

  • ARC를 사용하더라도, 순환 참조를 가지는 경우에는 영원히 메모리가 해지되지 않으므로 메모리 릭이 발생합니다.

  • 따라서 usafe 형태의 타입을 다룰 때에는 릭이 발생하지 않도록 주의해야 합니다.
  • 순환 참조를 만들 가능성이 있는 경우 순환 참조를 피하도록 주의해야 합니다.

  • leaks 명령으로 memgraph를 분석할 수 있습니다.
  • 출력은 총 240 바이트의 4 개의 leak이 있음을 알 수 있습니다.
  • 문제가 발생하는 ROOT CYCLE이 표시되며, 이를 통해 어떤 객체와 참조가 문제가 되는지 알 수 있습니다.
  • 또한 문제가 발생하는 시점의 malloc 호출 스택이 표시되어 호출 위치를 바로 찾아 수정할 수 있습니다.

  • 힙은 동적으로 할당된 개체가 저장되는 공간입니다.
  • 따라서 더 많은 객체를 할당할수록 힙의 사용량은 증가하게 됩니다.

  • 힙 사용량을 줄이려면 불필요한 할당을 제거해야 합니다.
  • 보다 작은 크기의 할당을 하는 것이 좋습니다.
  • 또한 사용하지 않는 객체는 메모리에서 제거해야 합니다.
  • 필요한 시점에만 객체를 할당해야 합니다.

  • vmmap -summary 수행하면 현재 메모리 할당 상태를 알 수 있습니다.

  • Physical footprint 항목에서 전체 사용량을 쉽게 알 수 있습니다.

  • Malloc 영역에서 할당된 메모리들의 크기를 확인할 수 있습니다.

  • DIRTY SIZE + SWAPPED SIZE = Memory footprint를 구할 수 있습니다.

  • 하나 이상의 memgraph가 있다면 diffFrom을 사용하여 새로 생긴 객체만을 추적할 수 있습니다.

  • -addresses 옵션과 CLASS_NAME, 메모리 크기를 필터로 지정하면 해당 타입의 객체들의 주소를 얻어낼 수 있습니다.

  • 얻어낸 주소를 --traceTree 옵션을 통해 질의하면 이 주소를 참조하는 객체들의 트리를 얻을 수 있습니다.

  • --referenceTree 명령을 사용하면 프로세스 내의 객체들의 참조 트리를 확인할 수 있습니다.

  • --groupByType 명령은 보다 유사한 유형을 그룹화하여 더 적은 출력을 보여주기 때문에 분석을 더 쉽게 할 수 있습니다.

  • -fullStacks 명령을 사용하면 원하는 객체의 생성 콜 스택을 얻어올 수 있습니다.

  • Page는 시스템이 프로세스에 할당하는 고정된 크기의 쪼갤 수 없는 메모리 구역입니다.
  • Fragmentation은 프로세스에 완전히 활용되지 않는 오염된 Page가 있을 때 발생합니다.

  • 초기에 프로그램을 실행해서 3개의 Page에 메모리를 할당받은 모습입니다.

  • 이후 2개의 객체의 할당을 해제하면 한 Page내에서 할당된 공간과 할당되지 않은 공간이 나뉩니다.

  • 이후 새로 할당을 받게 되었을 때 기존 Page의 빈 공간에 넣을 수 없는 경우 새 Page를 할당받게 됩니다.
  • 이런 경우의 빈 공간이 Fragmentation이 됩니다.

  • 빈 메모리 공간이 4개이며, 각 공간 사이가 할당된 공간이라 이어지지 않기 때문에 4개의 Fragmentaton 된 공간이 발생합니다.
  • 이 경우 Fragmentaton은 50%입니다.

  • 동일하게 4개의 Page가 할당이 되었지만 비슷한 수명의 객체들이 동시에 사라졌기 때문에 앞의 두 Page는 해제될 수 있습니다.
  • 따라서 빈 공간이 없기 때문에 Fragmentaton은 0%입니다.

 

  • %FRAG 항목에서 얼마나 많이 Fragmentation이 되었는지를 알 수 있습니다.
  • 일반적으로 DefaultMallocZone의 수치를 신경 써야 하지만, 이 경우에는 MSL이 켜져 있기 때문에 MallocStackLoggingLiteZone의 수치를 고려하면 됩니다.

 

  • Instruments tool의 Allocations항목에서 Allocation List항목을 보면 생성되고 삭제된 객체, 유지되는 객체들을 추적할 수 있습니다.

  • Fragmentation의 관점에서 삭제된 객체는 빈 공간을 만들고 유지되는 객체는 Fragmentation 된 Page를 유지합니다.

 

  • Fragmentation을 줄이는 좋은 방법은 수명이 비슷한 개체를 메모리에서 서로 가깝게 할당하는 것입니다.
  • 25% 이하로 Fragmentation을 유지하는 것이 좋습니다.
  • autorelease pool을 사용하면 범위를 벗어났을 때 객체들이 한 번에 사라지기 때문에 수명이 비슷해져서 Fragmentation을 줄일 수 있습니다.
  • 장기 실행되는 프로세스가 보다 많은 할당과 해제를 할 수 있기 때문에 더 Fragmentation에 취약할 수 있습니다.
  • Instruments에서 할당, 해제, 유지되는 객체들을 추적해서 개선할 수 있습니다.

 

  • 메모리 문제를 감지하는 과정의 시작은 새 기능마다 XCTest를 작성하는 것입니다.
  • 그다음 각 테스트에 대해 기준을 설정합니다.
  • 테스트를 실행하여 각각의 수행 결과를 얻습니다.
  • 수집된 ktrace 및 memgraph 파일을 사용하여 메모리 이슈를 조사합니다.

  • 진단을 위해서는 Leak 도구를 수행해서 결과를 얻는 것입니다.
  • vmmap -summary를 통해 통해 힙 메모리가 증가하는지 확인하고 heap -diffFrom을 증가하는 객체의 종류를 확인합니다.
  • 명확한 객체가 발견되면 heap -addresses를 통해 주소를 확인하고, 명확하지 않은 경우 leaks -referenceTree를 통해 대상을 찾습니다.
  • 이후 leaks -traceTree와 malloc_history -fullStacks를 통해 문제가 되는 메모리의 주소를 조사합니다.

  • leak을 탐지하여 제거해야 합니다.
  • 불필요하게 할당되는 공간을 최대한 줄여야 합니다.
  • 최대한 수명이 비슷한 객체를 연속적으로 할당하여 메모리의 Fragmentation이 발생하지 않도록 해야 합니다.

 

목차: https://nyancoder.tistory.com/2

Comments