Notice
Recent Posts
Recent Comments
Link
관리 메뉴

김종권의 iOS 앱 개발 알아가기

[iOS -swift] 6. Memory Deep Dive - Memory Footprint 프로파일링 방법 (3) (vmmap, leaks, heap, malloc_history) 본문

iOS 응용 (swift)

[iOS -swift] 6. Memory Deep Dive - Memory Footprint 프로파일링 방법 (3) (vmmap, leaks, heap, malloc_history)

jake-kim 2023. 12. 13. 01:00

* 가장 기초) iOS 메모리 기초 개념 - virtual memory, dirty memory, clean memory, compressed memory, swapped memory 이해하기 포스팅 글

 

1. Memory Deep Dive - iOS 메모리 운영체제 기초 (가상 메모리, 페이징, clean memory, dirty memory, compressed memory)

2. Memory Deep Dive - Memory를 줄여야 하는 이유 (+ 앱 메모리 사용량 아는 방법)

3. Memory Deep Dive - Memory Footprint (페이징, Compressed 메모리)

4. Memory Deep Dive - Memory Footprint 프로파일링 방법 (1) (Allocation, Leaks, VM Tracker, Virtual memory trace)

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이 발생하고 있는지도 알려주므로 매우 유용하게 사용이 가능

stack trace까지 제공

heap 명령어

  • heap 명령어는 사용하여 매우 큰 allocation이나 동일한 종류의 object들을 추적하는데 유용한 명령어
  • memory resource exception이 발생하면서 crash가 발생할때도 이 방법을 사용하여 heap을 프로파일링하는데 도움
heap App. memgraph

  • heap 명령어는 object의 개수별로 정렬되어, 가장 많은 object가 아닌 가장 큰 object로 보는것이 의미가 더욱 있으므로 sortBySize 플래그 추가
heap App. memgraph -sortBySize

위 로그에서는 CFString이 가장 크게 heap영역에 상주하고 있다는 확인이 가능

  • 위에서 얻은 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

* 참고

- https://developer.apple.com/videos/play/wwdc2018/416/

Comments