MINRYUL
류링류링
MINRYUL
전체 방문자
오늘
어제
  • 분류 전체보기
    • Swift
      • 학습
    • iOS
      • Toy-Project
      • 학습
      • Metal
    • CS
    • TIL

블로그 메뉴

  • 홈
  • 태그
  • 방명록

공지사항

인기 글

태그

  • Swift
  • ios
  • Existential any
  • CollectionView Cell
  • opaque type
  • dynamic frameworkm
  • TableView Cell
  • WWDC
  • RxCocoa
  • tuist
  • demangle
  • METAL
  • ViewStore
  • WWDC 2024
  • Existential type
  • Protocol
  • RxNimble
  • collectionView
  • RxSwift
  • RxTest
  • TDD
  • static framework
  • Any
  • Custom Calendar
  • Clean Architecture
  • BDD
  • urlsession
  • some
  • configuration_bulid_dir
  • AttributeText

최근 댓글

최근 글

티스토리

hELLO · Designed By 정상우.
MINRYUL
iOS/학습

WWDC 2024) 힙 메모리 분석하기

iOS/학습

WWDC 2024) 힙 메모리 분석하기

2024. 6. 25. 10:28

Measurement

힙을 이해하면 application의 virtual memory를 먼저 이해해야한다.

 앱이 시작되면서 virtual memory의 빈 주소 공간을 얻는다. 그리고, App executable, Linked libraries을 로드하고 읽기 전용 Resources 영역에 매핑한다. 또한 앱 실행시 앱은 각 스레드에 local var, temp var에 대한 stack 영역을 사용하며, dynamic, static 메모리는 힙 이라고 통칭되는 메모리 영역에 배치된다.

 

 힙은 단지 하나의 메모리 블록이 아니라 여러 virtual memory 영역으로 구현된다. Regions을 좀 더 확대하면, 각 Region이 개발 힙 alloc으로 구분된다. 내부적으로 이 Region은 16KB 메모리 페이지로 구성되지만 각 alloc은 더 크거나 작을 수 있다. 이러한 page는 Clean, Dirty, Swapped 세 가지 상태 중 하나일 수 있다.

 

 Clean 상태는 write되지 않은 깨끗한 메모리이다. 이는 alloc 되었지만 사용되지 않은 공간이거나 disk에서 read 전용으로 매핑된 파일을 나타내는 page일 수 있다. 시스템은 이러한 page를 언제든지 폐기시킬 수 있고, 다시 발생시킬 수 있으므로 매우 저렴한 상태이다.

 Dirty page는 application에서 최근에 사용한 메모리이다. Dirty page는 한동안 사용하지 않으면 버릴 수 없다. 메모리 부족이 있는 경우에 시스템은 메모리를 압축하거나 disk에 기록해서 교체할 수 있다. 따라서 오류가 발생할 수 있다.

Clean, Dirty, Swapped 세가지 상태 중 Dirty, Swapped 메모리만 application 메모리 공간에 포함되며, 대부분의 application에서 힙이 해당 공간의 대부분을 차지한다.

 

 힙 영역은 메모리 alloc이나 calloc, relloc과 같은 할당 기본 요소(Allocation primitives)를 사용해서 생성된 메모리이다. 대부분의 경우 이러한 함수를 직접 호출하지는 않지만 예를들어 Swift, Objc와 같은 언어들에서 클래스를 생성할때 컴파일러와 런타임은 이것들을 사용한다.

 

 할당을 사용하면 앱이 수명이 긴 메모리를 동적으로 할당할 수 있다. 할당은 명시적으로 free()를 할때까지 유지된다. 즉, 할당을 코드의 범위를 넘어서까지 지속할 수 있다. 이러한 함수는 몇가지 규칙을 따른다.

  1. 최소 할당 크기 및 정렬은 16-byte이다.
    1. 즉, 4 바이트를 요청하면 요청이 16바이트로 반올림된다. 그리고 보안으로 인해 대부분의 작은 할당은 0이 된다. 그것들이 free될때 런타임은 힙을 사용해서 수명이 긴 메모리를 할당한다.
    2. 예를 들어 Swift는 클래스 init 프로그램을 확장해서 일련의 Swift 런타임 함수를 호출하고 끝에서는 malloc을 호출하게 된다.

 

2. Malloc에는 몇 가지 디버그 기능도 있다.

  1.  각 할당에 대한 호출 스택과 타임스탬프를 기록하는 MallocStackLoggin이 있다.

 

2.  메모리 증가에 대해서 자세히 알려면 xcode의 메모리 보고서 기능으로는 충분하지 않고, Memory Graph Debugger를 사용해야한다. 이것은 모든 할당과 할당 간의 참조에 대한 캡처를 제공한다. MallockStackLogging을 사용하면 여기에 각 할당에 대한 역추적이 포함된다. 특정 할당을 집중해서 보고싶다면 사용하자.

 

3. leaks, heap, vmmap, mallock_history 등과 같은 command line tool 명령어는 macOS 및 시뮬레이터 프로세스를 직접 분석하거나 이미 캡처된 메모리 그래프를 사용해서 문제를 조사할 수 있다.

 

 

4. Profiling을 위한 툴들은 Instruments에 여러가지 템플릿들이 있다. Allocations tool의 경우 시간 경과에 따른 모든 alloc 및 free 이벤트를 기록하고, 통계 및 call tree를 모아 이를 코드로 추적하는데 도움을 준다.

 

 

Transient growth

메모리의 증가는 3가지의 이유로 좋지 않다.

 메모리의 급증올 인해 메모리 부족이 발생하고 시스템은 이에 반응한다. dirty 메모리 교환 및 압축, read 전용 메모리 삭제, 심지어는 백그라운드의 작업 종료까지 가능하다. 최악의 경우 이러한 메모리 증가는 앱의 종료를 의미할 수 있다. 이는 단기적인 영향일 수 있고, 장기적으로도 메모리 증가는 좋지 않다. 힙 메모리의 fragmentation이나 hole이 발생할 수 있기 때문이다.

 

 이를 조사하기 위해서는 특정 스파이크를 분석해서 낮은 지점에서 높은 지점까지의 Created 및 Still Living 할당을 찾을 수 있다. 또는 전체적으로 큰 범위를 선택해서 해당 범의의 생성 및 삭제된 모든 할당을 찾을 수 있다.

 

 Autorelease Pools은 일시적인 메모리 증가의 일반적인 원인이다. objc 는 이러한 pool을 사용해서 함수의 반환 값에 대한 수명을 연장한다. Autorelease pools은 release를 나중까지 지연해서 return 값을 유지한다. 자동으로 메모리를 할당하는 Swift를 사용하고 있지만 Swift에서 objc api를 사용하거나 이를 포함하는 프레임워크를 호출할 때 autorelease된 객체를 생성할 수 있음을 의미한다.

 

 스레드의 일반적으로 최상위 autoreleasepool이 있지만, 자주 Clean되지 않는다. 이는 코드가 루프에서 쉽게 발생하는 객체로 pool을 채울 때 매우 중요할 수 있다. 모든 반복해서 생성되는 객체는 동일한 pool로 autorelease되며, 필요 이상으로 오래 살아 있을 수 있다. 이 경우에 모든 루프가 끝날때 까지 객체는 살아 있게 된다. 나중에 pool은 lazy release를 보내고 많은 객체가 한번에 free 될 수 있다. 이를 해결하기 위해 local autoreleasepool의 범위를 지정할 수 있다. 이렇게 하면 더 적은 객체가 축적되고 참조를 추적하는데 도움이 된다. 즉, 필요한 content page가 더 적다는 것을 의미한다.

 

Persistent growth

 할당이 해제되지 않는 메모리를 의미한다. Instruments에 allocations tool에서 각 시간대 별로 mark를 새길 수 있고, 각 Generation별로 조사가 가능하다. 조사에서 특정 객체가 생성되고 해제되지 않은 것을 확인 했다면, Memory Graph Debugger에서 어떤 참조를 통해 메모리가 해제되지 않았는지 확인이 가능하다.

 

 

Swift나 objc에서는 type에 대한 정보가 명확하지만 C, C++에서는 참조 소유권 정보가 없으므로 Conservative(보수적) 참조로 나타나게 된다.

 

Leaked memory

3가지 누수가 있다.

  1. 프로그렘에서 접근할 수 있고, 나중에 다시 사용될 유용한 메모리.
  2. 도달 가능하고 사용할 수 있지만, 실제로 다시는 사용하지 않는 메모리. 이 메모리는 앱의 공간에 포함되어 낭비된다.
  3. 다시 사용할 수 없는 도달 불가능한 메모리. 객체의 순환 참조로 인해 마지막 포인터가 손실될 때 발생한다.

보통 escaping closure는 강력한 참조를 함으로 조심하자.

 

Performance

weak와 unowned 키워드는 strong 순환 참조를 피하기위해 Swift에서 사용하는 두 가지 일반적인 도구이다.

 weak의 경우 항상 optional type이며 대상이 해제된 이후에는 nil이 된다. weak를 사용하게 되면 참조 될 때 객체에 대한 Swift weak reference storage를 할당한다. 런타임에서는 weak reference가 늦게 제거될 수 있다.

 weak reference와 달리 unowned reference는 대상을 직접 보유한다. 이는 추가 메모리를 사용하지 않으며 weak reference보다 access하는데 시간이 덜 걸린다는 것을 의미한다. 하지만 참조된 객체가 더 일찍 해제되는 경우 crash가 결정적으로 발생하게 된다. 따라서 해당 객체가 얼마나 오래 지속될지 모른다면 weak reference의 작은 오버헤드는 가치가 있다.

 

'iOS > 학습' 카테고리의 다른 글

Error) Tuist Package Dependency "No files found at: ../PrivacyInfo.xcprivacy" 해결하기  (0) 2024.05.14
Error) Tuist 3.x -> 4.x 마이그레이션시 생기는 No such module 문제 해결하기  (0) 2024.05.04
[iOS] 푸시, 다이나믹 링크 등으로 실행될 때 디버깅하기  (0) 2023.08.28
Error) failed to demangle superclass of ‘…’ from mangled name ‘…’  (3) 2023.05.03
SwiftUI) iOS13 부터 지원하는 간단한 커스텀 Attribute Text 만들기  (0) 2023.02.15
  • Measurement
  • Transient growth
  • Persistent growth
  • Leaked memory
  • Performance
'iOS/학습' 카테고리의 다른 글
  • Error) Tuist Package Dependency "No files found at: ../PrivacyInfo.xcprivacy" 해결하기
  • Error) Tuist 3.x -> 4.x 마이그레이션시 생기는 No such module 문제 해결하기
  • [iOS] 푸시, 다이나믹 링크 등으로 실행될 때 디버깅하기
  • Error) failed to demangle superclass of ‘…’ from mangled name ‘…’
MINRYUL
MINRYUL
열심히 살자

티스토리툴바

단축키

내 블로그

내 블로그 - 관리자 홈 전환
Q
Q
새 글 쓰기
W
W

블로그 게시글

글 수정 (권한 있는 경우)
E
E
댓글 영역으로 이동
C
C

모든 영역

이 페이지의 URL 복사
S
S
맨 위로 이동
T
T
티스토리 홈 이동
H
H
단축키 안내
Shift + /
⇧ + /

* 단축키는 한글/영문 대소문자로 이용 가능하며, 티스토리 기본 도메인에서만 동작합니다.