고급 CPU 및 GPU 기술을 애플리케이션에 통합하는 방법에 대해 설명해보자
메모리와 CPU라는 두 가지 부족한 리소스를 사용하는 것에 초점을 맞춰서!
애플리케이션이 많은 CPU를 사용함에 따라 배터리 수명과 애플리케이션의 반응성에 부정적인 영향을 미칠 수 있다. 그리고 메모리를 사용할 수록 CPU 활용률도 증가하게 되고 배터리와 성능에 해로운 영향을 미친다.
UIImage 는 이미지 콘텐츠를 로드하는 역할
UIImageView 는 디스플레잉, 렌더링을 담당
렌더링 외에도 숨겨진 단계가 있다. 애플리케이션의 성능을 측정하기 위해서는 이 단계를 이해하는 것이 매우 중요하다. 이 단계는 디코딩이다.
디코딩을 논하기 전에 버퍼를 보자
이미지 버퍼의 각 요소는 이미지에서 단일 픽셀의 색상과 투명도를 설명한다.
따라서 메모리에 있는 이 버퍼의 크기는 이미지 사이즈에 비례한다.
버퍼 중 프레임 버퍼가 있다.
프레임 버퍼는 앱의 실제 렌더링된 출력을 유지하는 버퍼이다.
따라서 앱이 뷰 계층을 업데이트 하면 UIKit은 앱의 윈도우와 하위 뷰를 프레임 버퍼로 렌더링한다.
프레임버퍼는 디스플레이 하드웨어가 읽을 픽셀의 색상 정보를 제공한다.
마지막 작업은 고정된 간격으로 일어난다. 60fps(1초당 60프레임)이 일어날 수 있다. ProMotion Display인 iPad에서는 120fps가 빠르게 일어날 수 있다.
애플리케이션에서 아무것도 변경되지 않으면 디스플레이 하드웨어는 이전에 본 것과 같은 데이터를 프레임 버퍼에서 다시 가져온다.
하지만 UIImage에 새로운 이미지를 할당한다면 UIKit이 애플리케이션의 윈도우를 프레임 버퍼로 다시 렌더링한다.
그 후 디스플레이하드웨어가 프레임 버퍼에서 새 컨텐츠를 가져온다.
바이트 시퀀스를 포함하는 버퍼인 데이터 버퍼도 있다.
네트워크에서 혹은 디스크에서 로드한 이미지가 있고 이 이미지 파일을 데이터 버퍼가 포함한다.
데이터 버퍼는 해당 데이터 버퍼에 저장된 이미지의 크기를 설명하는 메타데이터로 부터 시작한다.
그런 다음, JPEG 혹은 PNG와 같은 형식으로 인코딩 된 이미지 데이터 자체를 포함한다.
즉 메타데이터 뒤에 오는 바이트는 실제로 이미지의 픽셀에 대한 어떤 것도 직접적으로 설명하지 않는다.
UIImageview의 렌더링으로 채워질 프레임 버퍼 영역이 있다.
이 이미지뷰에 이미지를 할당했다.
이미지 파일의 내용을 나타내는 데이터 버퍼를 가지고 있다.
우리는 프레임 버퍼에 픽셀당 데이터를 채워야 한다.
이를 위해 UIImage는 데이터 버퍼에 포함된 이미지의 크기와 동일한 크기의 이미지 버퍼를 할당한다.
그리고 JPEG, PNG 또는 다른 인코딩된 이미지 데이터를 픽셀 단위 이미지 정보로 변환하는 디코딩이라는 작업을 수행한다.
UIKit이 이미지 뷰에 렌더링 요청하면 이미지 버퍼에서 이미지 데이터를 프레임 버퍼로 복사할 때 이미지 데이터를 복사하고 크기를 조정한다.
이 디코딩 단계에서 큰 이미지의 경우 CPU를 많이 쓴다.
UIKIt이 이미지 뷰에 렌더링을 요청할 때마다 이 작업을 수행하는 대신 UIImage는 해당 이미지 버퍼에 고정되어 한 번만 작업을 수행된다.
따라서 디코딩되는 모든 이미지에 대해 앱이 영구적이고 큰 메모리 할당을 유지할 수 있다.
그리고 이 할당은 입력 이미지의 크기에 비례한다.
프레임 버퍼에서 실제로 렌더링되는 이미지 뷰의 크기가 반드시 필요한 것은 아니다.
그리고 이것은 성능에 꽤 부정적인 결과를 초래할 수 있다.
애플리케이션의 주소 공간이 큰 할당이 있으면 참조하려는 관련 컨텐츠들이 서로 분리될 수 있다(단편화)
메모리 사용량이 커지면 운영체제가 개입하며 물리적 메모리의 컨텐츠를 압축하기 시작한다.
CPU가 이 작업에 참여해야 하므로 애플리케이션 작업 외에도 CPU를 추가로 사용해야 한다.
제어할 수 없는 전역 CPU 사용량이 증가한다.
결국 애플리케이션이 물리적 메모리를 너무 많이 사용하기 시작하므로 OS가 프로세스 종료해야 할 수 있음
우선 순위가 낮은 백그라운드 프로세스부터 죽인다.
그리고 결국 앱이 너무 많은 메모리를 소비한다면, 앱 자체가 종료될 수 있다.
이러한 백그라운드 프로세스 중 일부는 사용자를 대신하여 중요한 작업을 수행하고 있을 수 있다.
그래서, 그 프로세스는 바로 다시 실행될지도 모른다.
따라서 애플리케이션이 짧은 시간동안에 메모리를 소비하더라도 CPU 활용률에 매우 큰 영향을 미칠 수 있다.
이미지를 표시할 이미지뷰가 이미지보다 작을 때!
다운샘플링 기술을 사용하면 메모리를 절약할 수 있다.
이미지 소스를 설정하고 축소 이미지(썸네일)를 만든 다음 디코딩된 이미지 버퍼를 UIImage로 캡처 후 해당 이미지를 이미지뷰에 할당한다.
더 작은 디코딩된 이미지 버퍼를 가질 것이기 때문에 더 낮은 메모리 사용량을 가진다.
`imageSource` : CGImageSource 객체 생성
`CFDictionary` : 옵션 딕셔너리
`ShouldCache` 는 중요한 플래그. 디코딩된 이미지를 캐시할지 여부
Core Graphics 프레임워크에 이 URL에 있는 파일에 저장되어 있는 정보를 나타내는 객체를 만들고 있다는 것을 나타냄.
이미지를 바로 디코딩하는게 아니라 이미지를 나타낼 객체를 먼저 만든다.
다음으로 렌더링할 스케일과 포인트 크기를 기반으로 섬네일용 옵션 딕셔너리를 만든다.
여기에는 몇 가지 옵션 있는데 이러한 옵션의 기능에 대해서는 알아서 찾아봐라!
`CacheImmediately` : 썸네일을 작성하도록 요청할 때 디코딩된 이미지 버퍼를 생성해야하는 정확한 시점을 Core Graphics에 전달하는 것. 기본값은 kCFBooleanFalse이며 이미지를 렌더링할 때만 디코딩 및 캐싱이 발생
따라서 디코딩을 위해 CPU가 필요한 타이밍을 정확하게 제어할 수 있다.
다음은 섬네일(CGImage, 축소판이미지)을 작성 후 UIImage를 리턴한다.
3000*2000 픽셀의 사진을 최적화 없이 렌더링했을 경우 31.5MB를 사용하지만
이 다운 샘플링 기술을 사용하여 실제 디스플레이 크기로 렌더링하면 메모리 사용량이 18.4MB까지 줄일 수 있다.
모두 이 기술을 사용해서 박수를 받으세요!
이 멋진 기술을 컬렉션뷰 셀의 이미지에 적용해보자!
이제 완벽해졌을까?
아니다. 여전히 문제가 있다.
스크롤할 때 멈춤 현상이 있다. 왜냐? 프레임 버퍼가 재렌더링 뙤기 전에 스크롤이 되고 사용자 입장에서는 앱이 버벅인다고 느껴질 수 있다.
그리고 셀을 보여줄 때마다 CPU1 사용량이 스파이크가 튀는 걸 확인할 수 있는데 이런 동작은 응답성뿐만 아니라 배터리 지속 시간에도 악영향을 준다. 왜냐하면 iOS는 CPU 수요가 원할하게 지속될 때 배터리의 전력 수요를 관리하는 데에 탁월하기 때문이다.
CPU 사용을 원할하게 하기 위해 사용할 수있는 두 가지 방법이 있다.
첫번째는 `prefeching`
두번째는 백그라운드 작업
이제 완벽해졌을까..? 아니다.
또 문제가 있다.
쓰레드 폭발이라는 문제이다.
시스템이 사용할 수 있는 CPU보다 많은 작업을 요청할 때,
즉 한번에 6-8개의 이미지를 보여주려고 하지만 CPU가 2개뿐이라면?
deadlock을 피하기 위해 GCD는 요청한 작업을 위한 새 스레드를 계속 만들 것이다.
그러면 CPU는 스레드 스위칭을 하기 위해 상당한 오버헤드가 걸릴 것이다.
따라서 SerialQueue를 사용한다.
프리 페치 메서드 내부에서 SerialQueue로 비동기적으로 디스패치한다.
'WWDC' 카테고리의 다른 글
WWDC21) Meet async/await in Swift (0) | 2022.07.11 |
---|