[WWDC18] iOS Memory Deep Dive 1

sokyte
daily-monster
Published in
11 min readMar 10, 2024

안녕하세요! 일욜 괴물 소깡이입니다.

오늘은 iOS 메모리에 관한 모든 것! 을 알아보기 위한 첫 번째 단계인 iOS Memory Deep Dive 세션을 보고 정리해 보도록 하겠습니다.

이번 글은 WWDC18의 iOS Memory Deep Dive를 시청하고 정리한 글입니다.

잘못된 내용이나 궁금하신 점이 있다면 댓글로 알려주세요.

Overview

메모리 그래프를 활용하여 앱의 메모리 Footprint를 자세히 살펴볼 수 있습니다. 또한 이미지의 실제 메모리 비용 (memory cost of an image)을 파악할 수 있습니다. 앱의 메모리 footprint를 줄이는 tip과 trick을 배울 수 있습니다.

세션 목차는 아래와 같습니다. 하나의 글에서 모든 내용을 다루기에는 조금 길어서 이번 글에서는 Memory Footprint까지 다루도록 하겠습니다.

  • Why reduce memory
  • Memory footprint
  • Tools for profiling footprint
  • Images
  • Optimizing when in background
  • Demo

세션에 대해 정리하기 전에 한 가지 알아두어야 할 점이 있습니다.

바로 가상 메모리 (Virtual Memory)입니다.

가상 메모리란?

  • CPU는 일을 처리하기 위해서 RAM을 사용하고 있는데, 이 RAM의 한정된 공간의 한계를 극복하기 위해서 디스크 (Disk) 공간에 메모리가 참조할 수 있는 주소를 맵핑하여 사용하는 것을 말합니다.
  • 즉, 메모리가 디스크의 공간을 사용하는데 마치 디스크 공간을 가상 메모리처럼 사용하는 것 (단, 캐싱 및 페이징을 통해 자주쓰는 데이터를 파악하여 디스크에서 메모리로 올리며 이런 것들을 통해 그냥 디스크만 쓰는 것보다 빠른 것)입니다.

세션에서도 나올 내용이지만, 위 개념과 함께 알아두어야 할 것은 페이징입니다.

페이징이란?

  • 디스크 공간을 가능하면 쪼개서 여러개의 Page라는 일정한 크기로 RAM이 사용할 수 있는 공간으로 분할하는 것으로
  • 이 분할된 공간에 접근하려면 분할된 공간의 주소가 Page Table이라는곳에 저장되고 RAM은 이 Page Table을 보고 디스크 공간에 접근합니다.

Why reduce memory

우리는 왜 메모리를 줄여야 할까요?

사용자들에게 더 나은 경험을 제공할 수 있기 때문입니다. 메모리를 줄이게 되면 App Launch가 더 빠르게 될 뿐만 아니라 시스템이 더 잘 동작하게 됩니다.

Memory Footprint

이 세션에서는 메모리를 줄이는 방법에 대해서 말하고 있는데요, 더 정확하게 말하자면 memory footprint을 줄이는 것을 말하고 있습니다.

우리가 한 가지 알아두어야 할 점은 모든 메모리가 동등하게 생성되는 것은 아니라는 것입니다. 그리고 이게 무슨 말인지 더 보려면 page에 대해 알아야 합니다.

page

메모리는 Page에 할당됩니다.

  • 메모리 페이지는 시스템에 의해 할당되며 Heap에 있는 여러 객체를 포함시킬 수 있습니다.
  • 일부 객체는 실제 여러 페이지에 걸쳐 있을 수 있습니다.
  • 일반적으로 페이지의 크기는 16KB이며
  • 깨끗한 페이지(clean page)와 더러운 페이지(dirty page)로 나뉩니다.
  • 앱의 메모리 사용량은 실제로 페이지 수페이지 크기를 곱한 값입니다.
  • pages 수 * page 사이즈 = 앱의 메모리 사용량(memory in use)

Clean and dirty pages

위에서 메모리에는 clean page와 dirty page로 나뉘어진다고 했었는데요, 어떤 페이지가 clean한 것이고 어떤 페이지가 dirty한 것인지 알아보겠습니다.

예를 들어 20,000개의 정수로 이뤄진 배열을 할당한다고 가정해 봅시다.

시스템은 여섯 개의 페이지를 할당할 수 있습니다. 이 페이지들은 할당될 때에는 깨끗합니다.

하지만 배열의 첫 번째 위치에 write를 시작하면 그 페이지는 더러워집니다. 그리고 아직 write를 하지 않은 페이지는 여전히 깨끗합니다.

Memory mapped files

이 파일들은 디스크에 있지만 메모리에 로드된 파일을 말합니다.

read-only 파일을 사용하는 경우에는 항상 깨끗한 파일입니다. 커널도 디스크에서 RAM으로 오고 나갈때 실제로 이들을 관리합니다.

?? 무슨소리 ??

예시를 한번 봅시다.

JPEG 파일이 있다고 가정해보고, 이 파일이 memory mapped in일 때, 50KB의 크기이며 메모리에 4 페이지를 차지한다고 해봅시다.

위와 같이 50 킬로바이트 크기의 JPEG가 있다면 메모리에 매핑될 때 실제로는 대략 네 개의 페이지에 매핑됩니다. 네 번째 페이지는 실제로 완전히 가득차 있지 않기 때문에 다른 용도로 사용될 수 있습니다.

(이때 마지막 4번째 페이지는 공간이 남아있으므로 다른 데이터 저장이 가능 (단, 해당 페이지가 clean 상태여야 사용이 가능))

하지만 이전의 세 페이지는 항상 시스템에 의해 제거될 수 있는 상태여야 합니다.

메모리가 dirty, clean 상태에 따라 알 수 있는 것

  • 프로퍼티를 사용할 때 setter로 선언하지 않고 getter로만 선언하면 page가 항상 clean 상태로 유지됩니다.

그리고 우리는 보통 앱의 footprint와 profile이 메모리의 dirty / compressed / clean한 segment를 가지고 있다고 말합니다.

Clean memory

클린 메모리는 페이지 아웃 될 수 있는 데이터로 위에서 언급한 memory-mapped 파일입니다.

  • 이미지, data Blob, training model, framework가 될 수 있습니다.
  • 위는 클린 메모리인 프레임워크를 보여준 것인데, 모든 프레임워크는 DATA CONST 섹션을 가지고 있으며
  • 일반적으로 clean하지만, 런타임에 메서드 뒤섞기와 같은 이상한 것들을 하면 dirty해질 수 있습니다.

Dirty memory

Dirty memory는 앱에 의해 쓰여진 모든 메모리를 말합니다.

이것들은 할당된 그 어떤 것이라도 될 수 있다. 객체, 문자열, 배열 등등이 여기 포함됩니다.

또한, 디코딩된 이미지 버퍼, 프레임워크가 될 수 있습니다.

프레임워크는 data 섹션과 data dirty 섹션을 가지고 있습니다.

*여기서, 클린 메모리와 더러운 메모리에 프레임워크가 두 번 언급된 것을 확인할 수 있습니다. 즉, 개발자가 link한 프레임워크는 memory와 dirty memory를 사용할 수 있습니다.

여기까지 clean 메모리와 dirty 메모리에 대해 정리하자면,

  • 가상 메모리(virtual memory) = clean memory + dirty memory

clean memory: 값이 변경되지 않은 깨끗한 메모리

  • 새로운 데이터를 쓰기 위해 사용
  • disk에서 로드될 수 있으며 실행 코드 or 읽기 전용 파일들을 의미

dirty memory: 값이 변경된 더러운 메모리

  • 쓰기 작업이 발생했기 때문에 다시 읽을 필요가 있거나, 변경된 내용을 디스크에 반영해야 할 때 사용
  • 힙, 싱글 톤 등 인스턴스들이 스택으로 채워진 메모리
  • clean memory는 새로운 작업을 위해 재사용되거나 더 이상 필요하지 않을 경우 해제될 수 있습니다.
  • dirty memory는 변경이 일어났으니, 디스크에 쓰여지거나 새로운 데이터로 업데이트될 수 있습니다.

Compressed memory

iOS는 전통적인 디스크 스왑 시스템을 사용하지 않고 Memory compressor를 사용합니다. 이것은 iOS7에서 소개된 것입니다.

Memory Compressor를 사용하면 엑세스 되지 않은 페이지를 압축하여 공간을 늘릴 수 있습니다. 하지만 엑세스 되면 메모리를 읽을 수 있도록 압축을 풀게 됩니다.

예를 들어 위와 같이 캐싱에 사용되는 딕셔너리가 있다고 가정해 봅시다. 현재 이 딕셔너리는 메모리의 세 페이지를 사용합니다.

그러나 오랫동안 해당 딕셔너리에 엑세스 하지 않은 경우, 시스템 공간이 필요할 때 이를 한 페이지로 압축하여 공간을 늘릴 수 있습니다.

이제 이 메모리는 압축되었고 공간을 절약하며 두 페이지를 더 확보할 수 있게 되었습니다.

그리고 나중에 엑세스 하게 된다면 압축이 풀려 다시 큰 공간을 차지하게 됩니다.

정리하자면,

compressed memory란?

  • iOS 7부터 나온 개념 (2013)으로
  • compressed memory가 나온 이유는 메모리 효율성을 높이기 위함입니다.
  • 메모리 사용공간 축소: Compressed Memory는 사용되지 않는 메모리 영역이나 중복 데이터를 찾아내어 압축
  • 가상 메모리 사용 시, disk에서 swap하는 비용을 최소화
  • 저전력 소모: 메모리 압축은 물리적 메모리의 사용을 최적화하면서도 전력 소모를 최소화 (효율적인 메모리 사용은 전력에도 영향을 미침)

compressed memory 동작 원리?

  • 시스템의 메모리가 채워지기 시작하면, compressed memory는 메모리에서 가장 최근에 사용된 항목을 자동으로 압축하여 원래 크기의 반으로 압축하고
  • 만약, 이 압축된 데이터들이 필요할 때 compressed memory가 즉시 압축 해제를 시도합니다.

Memory Warnings과 Compressed Memory

  • Compressed memory는 이전보다 더 많은 메모리를 사용할 수 있기 때문에 메모리 해제를 복잡하게 만듭니다.
  • 때문에 메모리 경고가 발생할 때 잠시 아무것도 캐싱하지 않거나, 일부 백그라운드 작업을 제한하는 작업이 권장합니다.

didReceiveMemoryWarning() 핸들링

  • 앱 메모리 경고 메서드가 있지만, 보통 핸들링하지 않는 경우가 있는데 실제로는 여기서 메모리 관련 처리가 필요합니다.
  • iOS7부터 Compressed Memory를 사용하여, 이전보다 더 많은 메모리를 사용할 수 있고 메모리 해제가 복잡한데 이 영향에 의하여 메모리 경고 처리가 필요합니다.
  • 캐싱을 제거하는 방법이 가장 쉽고 간편한 방법

Dictinoary가 아닌 NSCache로 캐싱해야 하는 이유

  • didReciveMemorywarning()을 다루기 전에 먼저, 캐싱을 할때 NSCache와 Dictinoary를 사용할 수 있는데 NSCache를 사용하는 것이 thread-safe하므로 NSCache가 안전합니다.
  • NSCache가 메모리를 할당하는 방식에 있어서 더욱 purgeable 합니다. (=더욱 메모리 제거가 쉬움)

메모리가 부족하다는 델리게이트 메서드가 있는데 여기에서 메모리를 관리하는 방법?

  • NSCache를 사용하고 있다면, didReceiveMemoryWarning에서 cache의 데이터들을 removeAllObjects()하여 핸들링이 필요합니다.
  1. 메모리 footprint의 한계치는 디바이스마다 다릅니다.
  2. extension 앱은 더 제약이 많습니다.
  3. 이 한계를 넘게되면 EXC_RESOURCE_EXCEPTION이 에러로 나오게 됩니다.

오늘은 여기까지해서

  • iOS 메모리 운영체제의 기초 (가상 메모리, 페이지, 메모리 유형 — clean, dirty, compressed)
  • 메모리를 줄여야 하는 이유
  • 메모리 footprint

에 대해서 알아보았습니다.

다음 글에서는 이러한 메모리를 프로파일 할 수 있는 툴에 대해서 알아보도록 하겠습니다.

--

--