cleanUrl: /programming/imag-and-memory-footprint

거의 대부분의 앱에 이미지가 들어갑니다. 그리고 이미지는 메모리를 아주 많이 차지합니다. 설령 수 kb밖에 되어보이지 않는 작은 파일에 들어가는 이미지라고 하더라도 말이죠.

앱에서 쓰이는 이미지는 대부분 수십~수백 킬로바이트수준입니다. 기가바이트 단위의 메모리를 가지고 있는 우리에게 킬로바이트 단위의 이미지들은 별로 부담되지 않는다고 생각 할 수도 있습니다. 하지만 이미지의 크기가 킬로바이트 단위인 것은 어디까지나 하드디스크에 파일로 저장 되었을 때의 이야기입니다. 실제로 파일이 메모리에 올라와 화면에 보여지는 과정에서, 이미지는 그보다 훨씬 더 큰 메모리를 차지하게 됩니다.

이미지가 화면에 보일 때 까지

먼저 이미지가 화면에 보이게 될 때 까지의 여정을 간단하게 되짚어 보겠습니다. 최초에 이미지는 하드디스크에 저장되어 있습니다. 저장된 파일의 이름을 “sample.jpg”라고 합시다. sample.jpg의 용량은 516kb이고, 크기는 1920×1440 입니다.

Screen-Shot-2019-12-04-at-4.04.52-PM.png

sample.jpg의 용량은 516kb이고, 크기는 1920×1440 입니다.

이미지의 크기가 1920×1440이라는 건, 이 이미지가 표현해야 하는 픽셀의 개수가 1920×1440= 2,764,800 개라는 이야기입니다. 그리고 각 픽셀은 빨간색, 초록색, 파란색, 투명도 총 4가지 값의 정보로 이루어져있고, 보통 각 색깔 정보는 1바이트로 표현합니다. 그러니 각 픽셀의 크기는 4바이트이고, 이 4바이트 픽셀이 2,764,800개 있으니 총 1920x1440x4= 11,059,200 바이트, 즉 약 10MB가 이 이미지를 표현하는데 필요하게 됩니다.

그런데 jpg의 크기는 MB는 커녕 516KB입니다. 어떻게 1920×1440짜리의 이미지가 516kb 안에 들어 갈 수 있는 걸까요? 간단히 설명하자면, jpg는 이미지가 “압축”된 형태이기 때문입니다. jpg이 어떻게 이미지를 압축하는지에 대해서는 JPEG ‘files’ & Colour (JPEG Pt1)- Computerphile를 참고해주세요.

결론적으로 우리는 정직하게 표현 되었을 때 최소한 10MB에 달하게 되는 데이터를 516KB에 저장하고, 또 네트워크를 통해 이를 주고 받을 수 있게 되었습니다. 사실 이런 종류의 압축 기술이 없었다면, 아무리 인터넷 속도가 빨라졌다고 해도 지금처럼 풍부한 컨텐츠를 소비 할 수는 없었을 것입니다. 문제는 이렇게 작게 압축된 데이터가 디스플레이에 표시 될 때입니다.

하드디스크의 파일이 디스플레이에 표시되기 위해선, 먼저 메모리에 올라가야 합니다. 이 과정을 로딩이라고 하지요. 로딩의 결과, jpg파일은 Data Buffer 라고 불리는 형태가 되어 메모리에 존재하게 됩니다. Buffer는 별게 아니고 그저 메모리의 연속된 공간을 차지하는 정보를 말합니다. Swift에서는 Data타입으로 표현되지요. 이 시점까지만 해도 용량이라는 차원에서는 이전과 큰 변화가 없습니다. 사용자 입장에서는 512kb짜리 사진을 다운받았고, 메모리에서도 512kb를 차지하고 있는 형국이지요.

하지만 이렇게 로딩된 DataBuffer가 실제로 뷰에 그려지기 위해서는 Image Buffer가 되어야 합니다. 이는 피할 수 없는 일이에요. 압축된 한글 파일을 이메일에 첨부해서 보낼 수는 있어도, 결국 그 한글 파일을 받아서 읽어보려면 압축을 풀어야 하잖아요? 마찬가지로 아무리 압축되었던 이미지라고 하더라도, 결국 1920×1440개의 픽셀을 표현하려면, 이 압축을 풀어야 합니다.

이 압축을 푸는 행위를,  decodinng 이라 하고, 그렇게 decoding된 형태가 바로 Image Buffer입니다. Image Buffer 형태가 되고 나서야, 운영체제는 각 픽셀 하나 하나를 어떻게 디스플레이에 표시해야 할 지를 알 수 있게 됩니다. 그리고 바로 이 시점에서 Image Buffer는, 유저의 메모리에서 10mb에 달하는 공간을 차지하게 되는 것이죠.

이상의 흐름을 그림으로 다시 표현하면 아래와 같습니다.

IMG_35DF22A45E59-1.jpeg

다운 샘플링을 통해 메모리 족적을 줄여봅시다.

그런데 사실 우리가 1920×1440 짜리 크기의 이미지를 다운받아도, 만약 이 이미지를 200×150사이즈의 뷰에 표시하는 거라면, 사실 (1920×1440 – 200×150) 만큼의 픽셀들은 무용지물이나 마찬가지입니다. 확대같은 것을 하지 않을 것이라면 더더욱 그렇지요.

그렇기 때문에 1920×1440짜리를 표시하는 DataBuffer를 재료로, 더 저화질의 200×150짜리 이미지를 만든 다음, 이 저화질의 이미지를 decoding 해서 훨씬 적은 용량의 image buffer를 만들게 된다면, 메모리를 훨씬 절약 할 수 있게 됩니다. 이 과정을 다운 샘플링 이라고 합니다. 이상의 내용을 그림으로 정리하면 아래와 같습니다.

IMG_AD11D2D21970-1.jpeg

주의할 점은 다운샘플링이 CoreGraphic을 사용하는, CPU자원을 많이 쓰는 녀석이라는 점입니다. 따라서 많은 다운샘플링이 main쓰레드에서 한 번에 발생하지 않도록 처리해 주어야 합니다.