일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | 3 | 4 | |||
5 | 6 | 7 | 8 | 9 | 10 | 11 |
12 | 13 | 14 | 15 | 16 | 17 | 18 |
19 | 20 | 21 | 22 | 23 | 24 | 25 |
26 | 27 | 28 | 29 | 30 | 31 |
- uiscrollview
- collectionview
- MVVM
- tableView
- 스위프트
- ribs
- Refactoring
- clean architecture
- 클린 코드
- ios
- Xcode
- rxswift
- map
- RxCocoa
- uitableview
- UITextView
- combine
- SWIFT
- Protocol
- swiftUI
- swift documentation
- Observable
- 리팩토링
- Human interface guide
- HIG
- 리펙토링
- Clean Code
- UICollectionView
- 애니메이션
- 리펙터링
- Today
- Total
김종권의 iOS 앱 개발 알아가기
[iOS -swift] 6. Memory Deep Dive - Memory Footprint 프로파일링 방법 (3) (vmmap, leaks, heap, malloc_history) 본문
[iOS -swift] 6. Memory Deep Dive - Memory Footprint 프로파일링 방법 (3) (vmmap, leaks, heap, malloc_history)
jake-kim 2023. 12. 13. 01:00
1. Memory Deep Dive - iOS 메모리 운영체제 기초 (가상 메모리, 페이징, clean memory, dirty memory, compressed memory)
2. Memory Deep Dive - Memory를 줄여야 하는 이유 (+ 앱 메모리 사용량 아는 방법)
3. Memory Deep Dive - Memory Footprint (페이징, Compressed 메모리)
5. Memory Deep Dive - Memory Footprint 프로파일링 방법 (2) (Xcode Memory Debugger, Memory Graph, Memgraph)
6. Memory Deep Dive - Memory Footprint 프로파일링 방법 (3) (vmmap, leaks, heap, malloc_history)
7. Memory Deep Dive - 이미지 로드 매커니즘, 이미지 핸들링 최적화 (UIGraphicsBeginImageContextWithOptions, UIGraphicsImageRenderer)
8. Memory Deep Dive - 이미지 리사이징, 이미지 다운 샘플링 (ImageIO, ImageSource)
9. Memory Deep Dive - 백그라운드에서 메모리 최적화하는 방법
복습)
- 이전 포스팅 글에서 보았듯이 Xcode의 memory graph에서 export memgraph를 사용하면 메모리 상태 정보를 담은 .memgraph파일 획득이 가능
- .memgraph 파일을 가지고 아래에서 알아볼 명령어를 통해 메모리 분석이 가능
vmmap 명령어
- process에 할당된 것들에 대한 virtual memory를 볼 수 있는 기능
vmmap App.memgraph
vmmap --summary App.memgraph
- process에 할당된 virtual memory 부분을 분석
ex) 위에서 다운받은 ExMemory123[31437].memgraph를 분석
vmmap ExMemory123\[31437\].memgraph
- 결과
vmmap을 통해 분석해야 하는 포인트
- dirty size와 swapped size가 중요
- dirty size가 중요한 이유: dirty size는 현재 사용중인 메모리 크기이므로 dirty size가 증가하고 있는지 파악하여 메모리를 줄이는 노력이 필요
- swapped size가 중요한 이유: swap이라는 의미는 메모리에 있는 용량이 부족하여 디스크에 접근하여 필요한 공간만큼 데이터를 swap했다는 의미이므로 swapped size가 크다면 디스크에 접근하는 시간으로 인해 성능에 문제가 생길 수 있음
- 중요한 포인트) Compressed 할당 방식
One important point of note is the swap size gives you the precompressed size of your data, not what it compressed down to.
(= 스왑 크기가 데이터를 압축한 크기가 아니라 사전 압축된 크기를 제공한다는 것)
힙 영역 분석
- vmmap 명령어를 입력하면 START - END, VSIZE, RSDNT, DIRTY, SWAP이라는 카테고리를 확인
- 이 영역이 바로 힙에 할당된 하나의 페이지 (이전 포스팅 글에서 알아보았듯이 1페이지 사이즈는 일반적으로 16K(
- dirty size를 구하기 위해서 아래 명령어를 통해 dirty memory를 합산
- 여기서 "-pages" 플래그가 있는데 이 플래그의 의미는 출력할 때 bytes말고 page 개수를 출력해달라는 의미
- "| grep '.dylib'" 의미는 동적 라이브러리를 검색
- awk의미는 awk 스크립트로 연결하여 dirty 영역의 합산을 구한 후 출력
vmmap -pages {mem_graph_name}.memgraph | grep '.dylib' | awk '{ sum += $6} END { print "Total Dirty Pages: " sum } '
- 이 dirty pages 정보를 가지고 앱에서 특정 동작을 했을 때 이 값을 비교하여 프로파일링하면 메모리 분석에 용이
Leaks 명령어
- leaks 단어와 memgraph 파일을 사용
- 원리: 런타임에 rooted 되지 않은 heap의 개체를 추적
- *rooted 되지 않는다는 의미: 보통 leak이 발생할 때 retain cycle에 의하여 발생하는데 이 때, A와 B사이에 retain cycle이 발생하면 A와 B 서로만 참조하고 있고 그 외의 인스턴스는 참조하지 않는 형태이므로 A와 B를 참조하고 있는 인스턴스는 없다, 즉 rooted되지 않는다고 표현
- leaks 명령어를 통해 leak을 찾아내고, leak이 있다는 의미는 절대 해제되지 않는 dirty memory 공간이라고 판단
leaks {name}.memgraph
결과)
ex2) 메모리 릭이 발생하는 예제 코드로 실험해보기
class ViewController: UIViewController {
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
DispatchQueue.main.asyncAfter(deadline: .now() + 3) {
let vc2 = VC2()
self.present(vc2, animated: true)
}
}
}
class VC2: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
view.backgroundColor = .lightGray
var person = Person(name: "John")
var pet = Pet(name: "Fluffy")
person.pet = pet
pet.owner = person
}
}
class Person {
var name: String
var pet: Pet?
init(name: String) {
self.name = name
print("\(name) is initialized")
}
deinit {
print("\(name) is deallocated")
}
}
class Pet {
var name: String
var owner: Person?
init(name: String) {
self.name = name
print("\(name) is initialized")
}
deinit {
print("\(name) is deallocated")
}
}
- VC2가 present되고 dismiss하더라도 Person과 Pet은 서로 retain cycle되어있기 때문에 leak이 발생할 것
- 이것의 memgraph를 프로파일링
- 가장 중요한 것은 stack trace까지 제공하여 어떤것에서 leak이 발생하고 있는지도 알려주므로 매우 유용하게 사용이 가능
heap 명령어
- heap 명령어는 사용하여 매우 큰 allocation이나 동일한 종류의 object들을 추적하는데 유용한 명령어
- memory resource exception이 발생하면서 crash가 발생할때도 이 방법을 사용하여 heap을 프로파일링하는데 도움
heap App. memgraph
- heap 명령어는 object의 개수별로 정렬되어, 가장 많은 object가 아닌 가장 큰 object로 보는것이 의미가 더욱 있으므로 sortBySize 플래그 추가
heap App. memgraph -sortBySize
- 위에서 얻은 CLASS_NAME을 사용하여 heap의 어느 주소에 저장되어 있는지 파익이 필요
- 아래 명령어를 통해 위에서 얻은 CLASS_NAME을 사용하여 더욱 분석이 가능
heap {name}.memgraph -addresses all | <classes-pattern>
(실행)
heap example_mem.memgraph -address CFString
- 여기서 0x로 시작하는 하단의 정보를 활용
- 이 주소 정보들을 아래에서 알아볼 malloc_history와 분석이 가능
malloc_history 명령어
- Xcode를 실행하면 malloc되는 것들을 내부적으로 기록할 수 있는 기능이 있는데, 이 옵션을 활성화하고 위에서 분석한 주소 정보를 통해 확인하여 메모리 분석이 가능
- scheme > run > diagnostics 탭 > Malloc Stack Logging을 체크
- malloc_history 명령어
- 메모리에 있는 인스턴스의 주소를 전달하고 이에 대한 stack trace 정보가 있다면 그 정보를 제공 (위 malloc stack logging 옵션을 켜야 확인이 가능하므로 주의)
malloc_history {name}.memgraph [address]
- malloc_history 사용 전에 malloc stack logging 옵션을 활성화하고난 후 다시 memgraph를 얻어서 address 획득
- 0x102d04310 이 정보로 malloc_history 사용
- 애플 내부적으로 사용하던 곳이므로 복잡한 코드가 표출
(WWDC2018에서 나오는 예제를 보면 아래처럼 stack trace가 있고 NoirFilter.apply라는 개발자가 입력한 코드 확인이 가능)
- 이를 통해 어떤 코드 부분에서 메모리가 심각하게 증가하고 있는지 역추적이 가능
정리
- 메모리에 저장될 때 큰 인스턴스가 있는지 확인하고 싶거나 메모리 exception이 발생하여 어떤 코드에서 발생하는지 역추적 하고 싶은 경우?
- vmmap, heap, malloc_history 명령어 사용 (+ 스킴에서 malloc stack logging 활성화 필요)
- retain cycle로 인한 메모리 릭 확인을 하고 싶은 경우?
- leaks 명령어
cf) 메모리 관리에 가장 심여를 기울여야할 부분은 "이미지"를 다루는 것이고 이는 다음 포스팅 글에서 계속...
* 전체 코드: https://github.com/JK0369/ExMemoryProfile
* 참고