안녕하세요. 개발하는 정주입니다.
오늘은 "autoreleasepool"에 대해 알아보겠습니다.
autoreleasepool의 개념이 낯설 수 있는데요.
ARC와 메모리 관리에 대한 개념이 어느정도 있어야 이해하기 편하실 겁니다.
ARC 포스팅 보러 가기
메모리 관리 개념 살펴보기
autoreleasepool 이란?
autoreleasepool이란 참조 카운트가 감소되는 것을 미루면서 나중에 감소되는 것을 보장받기 위한 기법입니다.
release는 호출되는 즉시 참조 카운트를 감소 시키는 것이고 AutoRelease는 나중에 감소 시킵니다. (근데 왜 auto인지..;;)
autoreleasepool 블록 내부의 객체는 AutoReleasePool에 등록됩니다.
autoreleasepool 블록이 종료되면 AutoReleasePool에 등록된 참조 카운트를 모두 감소(release) 시킵니다.
ARC가 충분히 메모리 관리를 해주기 때문에 일반적인 상황에서는 autoreleasepool를 이용하지 않아도 됩니다.
하지만 많은 임시 객체를 생성하는 루프를 작성하는 경우 autoreleasepool를 사용하면 최대 메모리 사용량을 줄일 수 있습니다.
메서드 내에서 release는 메서드 끝에서 진행됩니다.
만약 메서드 안에서 메모리를 많이 사용하는 임시 객체를 반복해서 만든다면 메모리 사용량이 계속 증가하게 됩니다.
이때, autoreleasepool 블록 내에 반복되는 임시 객체 생성 코드를 넣으면 반복이 끝날 때마다 release가 되어 할당 -> 해제 -> 할당 -> 해제가 반복되는 거죠.
autoreleasepool 직접 해보기
autoreleasepool의 장점을 직접 느껴봅시다.
테스트 방법
버튼을 누르면 UIImage를 10000번 생성하는 로직을 10회 반복합니다.
이때, autoreleasepool의 유무에 따른 메모리 최대 사용량을 비교해 봅시다.
각 시나리오의 메모리 변화량은 과정이 중요하기 때문에 영상 + 최종 결과 이미지로 준비했습니다.
UIImage의 init(named:)는 UIImage를 load 한 후 cache하기 때문에 올바른 결과를 관측하기 어렵습니다.
따라서 init(contentsOfFile:)을 사용하였으며 둘의 차이에 대해서도 아래에서 언급하겠습니다.
초기의 메모리 상태입니다.
결과 분석에 참고하면 좋겠습니다.
autoreleasepool 미사용
autoreleasepool을 사용하지 않았을 때입니다.
@IBAction func memoryTest(sender: UIButton) {
let imgPath = Bundle.main.path(forResource: "profile-circle", ofType: ".png")!
for i in 0..<10 {
print("----- test \(i) -----")
autoreleasepool {
for _ in 0..<10000 {
let image: UIImage = UIImage(contentsOfFile: imgPath)!
}
}
}
}
메서드 내부에서 10000 * 10개의 UIImage 인스턴스가 생성됩니다.
이 인스턴스는 10000 * 10회 반복이 끝날 때까지 메모리에 존재하고 메서드가 끝나면 한 번에 해제됩니다.
메모리 변화는 어떻게 되었는지 볼까요?
test 9가 출력되기 전까지는 메모리 사용량이 계속 증가하여 최대 메모리 사용량은 1.58GB까지 올라갔습니다.
모든 반복이 종료되고 메서드가 종료된 후 한 번에 release 되었습니다.
이렇게 최대 메모리 사용량이 높다면 기기의 메모리가 부족해져 앱이 강제 종료 되는 상황이 생길 수 있습니다.
autoreleasepool 사용
autoreleasepool을 사용하면 메모리 사용량이 어떻게 변할지 알아봅시다.
이 테스트를 UIImage로 할 경우 UIImage(named:)가 아니라 UIImage(contentOfFile:)을 사용해야 합니다. 주의해주세요.
이유는 다음 소단원에 있습니다.
@IBAction func memoryTest(sender: UIButton) {
let imgPath = Bundle.main.path(forResource: "profile-circle", ofType: ".png")!
for i in 0..<10 {
print("----- test \(i) -----")
autoreleasepool {
for _ in 0..<10000 {
let image: UIImage = UIImage(contentsOfFile: imgPath)!
}
}
}
}
내부 반복문을 autoreleasepool로 감싸서 내부 반복문이 끝날 때마다, 즉 10000회 반복이 끝날 때마다 release를 해보겠습니다.
어떤 변화가 있을까요?
놀랍게도 최대 메모리 사용량이 170.5MB로 사용하지 않았을 때보다 10배 더 낮은 수치를 보여주었습니다.
영상을 보면 test 문이 찍힐 때마다 메모리가 해제되고 다음 반복문이 수행될 때 다시 높아지는 것을 볼 수 있습니다.
할당 -> 해제 -> 할당 -> 해제를 반복하면서 코드가 수행되기 때문에 최대 메모리 사용량이 낮아진 것입니다.
UIImage(named:)를 쓴 경우
UIImage(named:)는 이미지를 load하고 cache 합니다.
즉, 두 번째로 load할 때는 cache 데이터를 사용하기 때문에 굉장히 빨리 불러오므로 메모리 변화량 그래프를 관찰하기 어렵습니다.
반대로 UIImage(contentOfFile:)는 캐싱하지 않고 매번 이미지를 load하기 때문에 그래프를 관찰하기 좋습니다.
실제 개발에서는 몰라도 이번 테스트에서 만큼은 UIImage(contentOfFile:)가 적절한 것이죠.
그럼 UIImage(named:)를 쓰면 어떻게 되는지 봅시다.
for i in 0..<10 {
print("----- test \(i) -----")
autoreleasepool {
for _ in 0..<1_000_000 {
let image: UIImage = UIImage(named: "profile-circle")!
}
}
}
횟수를 10000에서 100만으로 늘리고 UIImage(named:)를 사용한 예제입니다.
100만으로 늘린 이유는 100배는 해야 그래프 관찰이 되기 때문입니다 ㅎ;;
결과 영상을 봐봅시다.
처음에는 정상적으로 높아졌다 떨어지고 다시 높아지지 않습니다.
동작 자체가 잘못된 것은 아니고 image를 load하는 시간이 빨라서 반복문이 빨리 끝나는 것입니다.
이는 할당과 해제도 빠르게 반복된다는 것을 의미하기 때문에 그래프가 저모양인 것입니다.
실제로 영상의 로그를 보면 UIImage(contentOfFile:)보다 test가 찍히는 시간이 훨씬 빠릅니다.
이제 UIImage(contentOfFile:)를 사용해야 하는 이유를 알겠죠?
autoreleasepool에 대한 내 생각
근데 한 가지 의문은 autoreleasepool이 실제 개발에서 큰 의미가 있는가 입니다.
이번 테스트에서는 10만 개의 UIImage를 캐싱 없이 테스트했기 때문에 당연히 큰 차이가 날 것입니다.
하지만 실제 개발에서는 10만 개는 고사하고 1000개는 로드할까요? 그리고 캐싱도 당연히 쓰겠죠.
물론 UIImage 뿐만 아니라 URLSession 등에서도 쓰인다고 합니다만...
그럼에도 큰 의미가 있는가는 잘 모르겠습니다.
메모리를 하나하나 살펴보며 autoreleasepool을 쓰는 것보다
적절히 메서드를 나누고 Thread 수명을 짧게 관리하는 것이 더 효과적이지 않을까? 하는 생각이 듭니다.
혹시 제 생각이 틀렸다면.. 댓글로 한 수 가르쳐주시면 감사합니다 ㅎㅎ
마무리
이번 포스팅에서는 ARC에 이어서 메모리 관리 개념인 autoreleasepool에 대해 배워봤습니다.
앱의 성능 최적화를 위해서는 메모리에 대해 빠삭해야 하므로 메모리 공부는 해도해도 부족한 것 같습니다.
개념만 익히는게 아니라 직접 테스트 해보면서 공부하니 재미가 있어서 다행입니다.
재미도 없었다면 참 힘들었을 거에요 ㅎㅎ
다음 포스팅도 열심히 준비하겠습니다.
감사합니다!
참고
https://developer.apple.com/documentation/foundation/nsautoreleasepool
https://wlgusdn700.tistory.com/30
https://developer.apple.com/documentation/xcode/gathering-information-about-memory-use
https://developer.apple.com/documentation/uikit/uiimage
아직은 초보 개발자입니다.
더 효율적인 코드 훈수 환영합니다!
공감과 댓글 부탁드립니다.