https://developer.apple.com/videos/play/wwdc2024/10173/
해당 포스트는 위 영상을 기반으로 개인적으로 메모/정리한 글입니다.
개요
Heap - dynamic memory- 의 기초를 살펴보자. Heap memory 를 측정하고 줄일 수 있는 방법을 알아보자.
Heap
malloc() 에 의해 할당되는 메모리.
참조 유형이 저장되는 곳.
memory limit 이슈를 발생시킬 수 있는 곳.
- clean: 아직 쓰여지지 않은 메모리. 할당되고 사용하되지 않은 메모리 or 읽기 전용 맵핑
- dirty: 최근에 읽힌 메모리. 메모리 워닝 시 압축(swapped)되거나 디스크에 쓰여질 수 있음
- swapped: 압축된 상태
Measuring your heap
MallocStackLogging
Xcode Memory Report
Memory Graph Debugger
CLI Tools
Instrunments
- Allocations: 모든 alloc 의 히스토리를 기록한다
- VM Tracker: Allocations 에 포함됨. 모든 가상 메모리의 시간에 따른 스냅샷을 찍는다
- Leaks: app 메모리의 시간경과에 따른 스냅샷을 찍는다
Dealing with transient growth
순간적으로 증가했다 감소하는 메모리를 해결하기.
메모리 스파이크를 주의해야 하는 이유
- 메모리 Pressure 를 유발시켜서 앱의 성능에 영향을 줌(ex: 백그라운드 테스크 종료 등)
- OOM 크래시
- Heap memory regions 에 fragmentation 이나 hole을 유발함
두 가지 측정 방법이 있다: 부분적으로 살펴보기 or 전체를 살펴보기
트랙킹 예시
Autorelease pool
- Swift 에서 알아서 관리해주는 임시 오브젝트 관리소 같은 거. 이게 보통 temp 메모리 증가를 유발한다
- (ex) `print(”Now is \(Date.now)”)` 코드에서 .description 을 위한 autoreleased String 이 생성되고 이것이 heap에 들어감.
- loop 에서 대부분 문제가 된다. autorelease pool 의 scope 가 끝나야 release 를 하기 때문에 loop 가 끝날 때 까지는 계속 heap 에 쌓이게 되기 때문
위와 같은 이슈를 해결하는 방법 → autoreleasepool 의 scope 를 명시적으로 지정해주면 된다.
(모든 loop 가 끝날 때까지 굳이 String 을 heap 에 유지할 필요가 없다.)
Tracking persistent growth
지속적으로 높아지는 메모리를 추적해보자.
Generations 옵션을 선택하면 Generation 의 스냅샷을 찍을 수 있다.
Memory graph debugger
“Why is this object still around” 를 대답해주는 툴.
MallocStackLogging 을 켜두면 각 allocation의 backtraces 도 트랙킹 가능하다 (매우 도움)
Fixing memory leaks
Reachability → 모든 메모리는 non-weak 참조를 통해 도달(reachable)할 수 있어야 한다.
Heap에서의 메모리 종류
- Useful memory: 미래에 다시 쓰일 Reachable allocations
- Abandoned memory: 다시 쓰일 일 없는 Reachable allocations. 무분별한 캐싱 등으로 발생
- Leaked memory: 해제할 수 있는 소유자가 없는 Unreachable allocations.
Memory graph debugger
Navigator 에서 선택 시 문제를 확인 가능하다.
Leak FAQ
- Q. 왜 툴들이 Leak 감지를 완벽하게 하지 못하나요?
- A. 툴에서 타입 정보를 알 수 없는 메모리가 많다. 그리고 C언어는 unmangeed pointer 를 허용하기 때문에 툴은 포인터처럼 보이지만 실제로는 아닌 것들을 허용해주어야 한다. 이걸 Conservative scanning (보수적인 스캐닝?)이라고 한다.
- Q. Leak 개수가 시간에 경과함에 따라 오르락 내리락 하던데 이건 왜 그런가요?
- A. Heap은 noisy하고 random 하기 때문에 Conservative reference 는 비결정적(non-deterministic)이다. 따라서 leak 발견이 시간이 지남에 따라 늘어날 수도 다시 줄어들 수도 있다.
- Q. ‘noreturn’ 함수가 leaking 으로 판단될 때가 있는데 이건 왜 이런가요?
- A. C에서의 `noreturn` 혹은 Swift의 `Never Type return` 을 컴파일러가 호출할 때는 어차피 함수에서 리턴하는 게 없으니까 cleanup을 하게 된다. 그러나 이것을 하지 못할 때 Leak으로 판단된다.
Improving runtime performance
weak vs unowned
- weak: 항상 optional type. destination 이 deinit 될 때 nil이 됨. (nil이 되더라도 문제 없음)
- unowned: non-optional. force-unwrapped weak 같은 거. 직접적으로 destination 을 잡고있기 때문에 weak 보다 cost 면에서 효율적이다. 그러나 weak 와 달리 destination이 deinit 되었을 때 unowned 에 접근하면 Crash/bloat (주의할 것!!)
결론: Weak 는 Default 로 쓰기 안전하다. Unowned 는 Memory/Runtime Cost를 아껴준다(하지만 안전을 확신할 수 없는 경우 Crash를 유발하니 쓰지말기!).
참고: weak, unowned 등의 정보를 memory graph 에서 볼 수 없다면 Xcode 셋팅에서 Reflection Metadata Level 이 default 로 잘 설정되어 있는지 확인해보기.
유의할 점
Performance 를 측정하기 위한 관찰을 시작하면 그 자체로도 Observation cost 가 발생한다. 예를 들어 MallocStackLogging 을 활성화하면 오직 그 로깅 때문에 memory 를 씀. Memory Graph Debugger 로 snapshot을 찍으면 Suspend 가 발생함 등등.
'WWDC > 2024' 카테고리의 다른 글
Discover media performance metrics in AVFoundation (0) | 2024.08.08 |
---|---|
Explore Swift performance (0) | 2024.08.08 |
Demystify explicitly built modules (추천세션!) (0) | 2024.08.08 |
Migrate your app to Swift 6 (추천세션!) (0) | 2024.08.08 |
Consume noncopyable types in Swift (0) | 2024.08.08 |