iOS圖片加載速度極限優(yōu)化—FastImageCache解析

FastImageCache是Path團(tuán)隊(duì)開發(fā)的一個(gè)開源庫,用于提升圖片的加載和渲染速度身辨,讓基于圖片的列表滑動(dòng)起來更順暢障簿,來看看它是怎么做的。


優(yōu)化點(diǎn)


iOS從磁盤加載一張圖片蟹腾,使用UIImageVIew顯示在屏幕上痕惋,需要經(jīng)過以下步驟:


從磁盤拷貝數(shù)據(jù)到內(nèi)核緩沖區(qū)

從內(nèi)核緩沖區(qū)復(fù)制數(shù)據(jù)到用戶空間

生成UIImageView,把圖像數(shù)據(jù)賦值給UIImageView

如果圖像數(shù)據(jù)為未解碼的PNG/JPG娃殖,解碼為位圖數(shù)據(jù)

CATransaction捕獲到UIImageView layer樹的變化

主線程Runloop提交CATransaction值戳,開始進(jìn)行圖像渲染

? ?6.1 如果數(shù)據(jù)沒有字節(jié)對齊,Core Animation會(huì)再拷貝一份數(shù)據(jù)炉爆,進(jìn)行字節(jié)對齊堕虹。


? ?6.2 GPU處理位圖數(shù)據(jù),進(jìn)行渲染芬首。


FastImageCache分別優(yōu)化了2,4,6.1三個(gè)步驟:


使用mmap內(nèi)存映射鲫凶,省去了上述第2步數(shù)據(jù)從內(nèi)核空間拷貝到用戶空間的操作。

緩存解碼后的位圖數(shù)據(jù)到磁盤衩辟,下次從磁盤讀取時(shí)省去第4步解碼的操作螟炫。

生成字節(jié)對齊的數(shù)據(jù),防止上述第6.1步CoreAnimation在渲染時(shí)再拷貝一份數(shù)據(jù)艺晴。

接下來具體介紹這三個(gè)優(yōu)化點(diǎn)以及它的實(shí)現(xiàn)昼钻。


內(nèi)存映射


平常我們讀取磁盤上的一個(gè)文件,上層API調(diào)用到最后會(huì)使用系統(tǒng)方法read()讀取數(shù)據(jù)封寞,內(nèi)核把磁盤數(shù)據(jù)讀入內(nèi)核緩沖區(qū)然评,用戶再從內(nèi)核緩沖區(qū)讀取數(shù)據(jù)復(fù)制到用戶內(nèi)存空間,這里有一次內(nèi)存拷貝的時(shí)間消耗狈究,并且讀取后整個(gè)文件數(shù)據(jù)就已經(jīng)存在于用戶內(nèi)存中碗淌,占用了進(jìn)程的內(nèi)存空間。


FastImageCache采用了另一種讀寫文件的方法,就是用mmap把文件映射到用戶空間里的虛擬內(nèi)存亿眠,文件中的位置在虛擬內(nèi)存中有了對應(yīng)的地址碎罚,可以像操作內(nèi)存一樣操作這個(gè)文件,相當(dāng)于已經(jīng)把整個(gè)文件放入內(nèi)存纳像,但在真正使用到這些數(shù)據(jù)前卻不會(huì)消耗物理內(nèi)存荆烈,也不會(huì)有讀寫磁盤的操作,只有真正使用這些數(shù)據(jù)時(shí)竟趾,也就是圖像準(zhǔn)備渲染在屏幕上時(shí)憔购,虛擬內(nèi)存管理系統(tǒng)VMS才根據(jù)缺頁加載的機(jī)制從磁盤加載對應(yīng)的數(shù)據(jù)塊到物理內(nèi)存,再進(jìn)行渲染岔帽。這樣的文件讀寫文件方式少了數(shù)據(jù)從內(nèi)核緩存到用戶空間的拷貝玫鸟,效率很高。


解碼圖像


一般我們使用的圖像是JPG/PNG犀勒,這些圖像數(shù)據(jù)不是位圖鞋邑,而是是經(jīng)過編碼壓縮后的數(shù)據(jù),使用它渲染到屏幕之前需要進(jìn)行解碼轉(zhuǎn)成位圖數(shù)據(jù)账蓉,這個(gè)解碼操作是比較耗時(shí)的,并且沒有GPU硬解碼逾一,只能通過CPU铸本,iOS默認(rèn)會(huì)在主線程對圖像進(jìn)行解碼。很多庫都解決了圖像解碼的問題遵堵,不過由于解碼后的圖像太大箱玷,一般不會(huì)緩存到磁盤,SDWebImage的做法是把解碼操作從主線程移到子線程陌宿,讓耗時(shí)的解碼操作不占用主線程的時(shí)間锡足。


FastImageCache也是在子線程解碼圖像,不同的是它會(huì)緩存解碼后的圖像到磁盤壳坪。因?yàn)榻獯a后的圖像體積很大舶得,F(xiàn)astImageCache對這些圖像數(shù)據(jù)做了系列緩存管理,詳見下文實(shí)現(xiàn)部分爽蝴。另外緩存的圖像體積大也是使用內(nèi)存映射讀取文件的原因沐批,小文件使用內(nèi)存映射無優(yōu)勢,內(nèi)存拷貝的量少蝎亚,拷貝后占用用戶內(nèi)存也不高九孩,文件越大內(nèi)存映射優(yōu)勢越大。


字節(jié)對齊


Core Animation在圖像數(shù)據(jù)非字節(jié)對齊的情況下渲染前會(huì)先拷貝一份圖像數(shù)據(jù)发框,官方文檔沒有對這次拷貝行為作說明躺彬,模擬器和Instrument里有高亮顯示“copied images”的功能,但似乎它有bug,即使某張圖片沒有被高亮顯示出渲染時(shí)被copy宪拥,從調(diào)用堆棧上也還是能看到調(diào)用了CA::Render::copy_image方法:


fastImageCache1.png


那什么是字節(jié)對齊呢仿野,按我的理解,為了性能江解,底層渲染圖像時(shí)不是一個(gè)像素一個(gè)像素渲染设预,而是一塊一塊渲染,數(shù)據(jù)是一塊塊地取犁河,就可能遇到這一塊連續(xù)的內(nèi)存數(shù)據(jù)里結(jié)尾的數(shù)據(jù)不是圖像的內(nèi)容鳖枕,是內(nèi)存里其他的數(shù)據(jù),可能越界讀取導(dǎo)致一些奇怪的東西混入桨螺,所以在渲染之前CoreAnimation要把數(shù)據(jù)拷貝一份進(jìn)行處理宾符,確保每一塊都是圖像數(shù)據(jù),對于不足一塊的數(shù)據(jù)置空灭翔。大致圖示:(pixel是圖像像素?cái)?shù)據(jù)魏烫,data是內(nèi)存里其他數(shù)據(jù))


fastImageCache2.png


塊的大小應(yīng)該是跟CPU cache line有關(guān),ARMv7是32byte肝箱,A9是64byte哄褒,在A9下CoreAnimation應(yīng)該是按64byte作為一塊數(shù)據(jù)去讀取和渲染,讓圖像數(shù)據(jù)對齊64byte就可以避免CoreAnimation再拷貝一份數(shù)據(jù)進(jìn)行修補(bǔ)煌张。FastImageCache做的字節(jié)對齊就是這個(gè)事情呐赡。


實(shí)現(xiàn)


FastImageCache把同個(gè)類型和尺寸的圖像都放在一個(gè)文件里,根據(jù)文件偏移取單張圖片骏融,類似web的css雪碧圖链嘀,這里稱為ImageTable。這樣做主要是為了方便統(tǒng)一管理圖片緩存档玻,控制緩存的大小怀泊,整個(gè)FastImageCache就是在管理一個(gè)個(gè)ImageTable的數(shù)據(jù)。整體實(shí)現(xiàn)的數(shù)據(jù)結(jié)構(gòu)如圖:


fastImageCache3.png


一些補(bǔ)充和說明:


ImageTable


一個(gè)ImageFormat對應(yīng)一個(gè)ImageTable误趴,ImageFormat指定了ImageTable里圖像渲染格式/大小等信息霹琼,ImageTable里的圖像數(shù)據(jù)都由ImageFormat規(guī)定了統(tǒng)一的尺寸,每張圖像大小都是一樣的凉当。

一個(gè)ImageTable一個(gè)實(shí)體文件碧囊,并有另一個(gè)文件保存這個(gè)ImageTable的meta信息。

圖像使用entityUUID作為唯一標(biāo)示符纤怒,由用戶定義糯而,通常是圖像url的hash值。ImageTable Meta的indexMap記錄了entityUUID->entryIndex的映射泊窘,通過indexMap就可以用圖像的entityUUID找到緩存數(shù)據(jù)在ImageTable對應(yīng)的位置熄驼。

ImageTableEntry


ImageTable的實(shí)體數(shù)據(jù)是ImageTableEntry像寒,每個(gè)entry有兩部分?jǐn)?shù)據(jù),一部分是對齊后的圖像數(shù)據(jù)瓜贾,另一部分是meta信息诺祸,meta保存這張圖像的UUID和原圖UUID,用于校驗(yàn)圖像數(shù)據(jù)的正確性祭芦。

Entry數(shù)據(jù)是按內(nèi)存分頁大小對齊的筷笨,數(shù)據(jù)大小是內(nèi)存分頁大小的整數(shù)倍,這樣可以保證虛擬內(nèi)存缺頁加載時(shí)使用最少的內(nèi)存頁加載一張圖像龟劲。

圖像數(shù)據(jù)做了字節(jié)對齊處理胃夏,CoreAnimation使用時(shí)無需再處理拷貝。具體做法是CGBitmapContextCreate創(chuàng)建位圖畫布時(shí)bytesPerRow參數(shù)傳64倍數(shù)昌跌。

Chunk


ImageTable和實(shí)體數(shù)據(jù)Entry間多了層Chunk仰禀,Chunk是邏輯上的數(shù)據(jù)劃分,N個(gè)Entry作為一個(gè)Chunk蚕愤,內(nèi)存映射mmap操作是以chunk為單位的答恶,每一個(gè)chunk執(zhí)行一次mmap把這個(gè)chunk的內(nèi)容映射到虛擬內(nèi)存。為什么要多一層chunk呢萍诱,按我的理解悬嗓,這樣做是為了靈活控制mmap的大小和調(diào)用次數(shù),若對整個(gè)ImageTable執(zhí)行mmap裕坊,載入虛擬內(nèi)存的文件過大包竹,若對每個(gè)Entry做mmap,調(diào)用次數(shù)會(huì)太多碍庵。


緩存管理


用戶可以定義整個(gè)ImageTable里最大緩存的圖像數(shù)量,在有新圖像需要緩存時(shí)悟狱,如果緩存沒有超過限制静浴,會(huì)以chunk為單位擴(kuò)展文件大小,順序?qū)懴氯ゼ方ァH绻殉^最大緩存限制苹享,會(huì)把最少使用的緩存替換掉,實(shí)現(xiàn)方法是每次使用圖像都會(huì)把UUID插入到MRUEntries數(shù)組的開頭浴麻,MRUEntries按最近使用順序排列了圖像UUID得问,數(shù)組里最后一個(gè)圖像就是最少使用的。被替換掉的圖片下次需要再使用時(shí)软免,再走一次取原圖—解壓—存儲(chǔ)的流程宫纬。


使用


FastImageCache適合用于tableView里緩存每個(gè)cell上同樣規(guī)格的圖像,優(yōu)點(diǎn)是能極大加快第一次從磁盤加載這些圖像的速度膏萧。但它有兩個(gè)明顯的缺點(diǎn):一是占空間大漓骚。因?yàn)榫彺媪私獯a后的位圖到磁盤蝌衔,位圖是很大的,寬高100*100的圖像在2x的高清屏設(shè)備下就需要200*200*4byte/pixel=156KB蝌蹂,這也是為什么FastImageCache要大費(fèi)周章限制緩存大小噩斟。二是接口不友好,需預(yù)定義好緩存的圖像尺寸孤个。FastImageCache無法像SDWebImage那樣無縫接入U(xiǎn)IImageView剃允,使用它需要配置ImageTable,定義好尺寸齐鲤,手動(dòng)提供的原圖斥废,每種實(shí)體圖像要定義一個(gè)FICEntity模型,使邏輯變復(fù)雜佳遂。


FastImageCache已經(jīng)屬于極限優(yōu)化营袜,做圖像加載/渲染優(yōu)化時(shí)應(yīng)該優(yōu)先考慮一些低代價(jià)高回報(bào)的優(yōu)化點(diǎn),例如CALayer代替UIImageVIew丑罪,減少GPU計(jì)算(去透明/像素對齊)荚板,圖像子線程解碼,避免Offscreen-Render等吩屹。在其他優(yōu)化都做到位跪另,圖像的渲染還是有性能問題的前提下才考慮使用FastImageCache進(jìn)一步提升首次加載的性能,不過字節(jié)對齊的優(yōu)化倒是可以脫離FastImageCache直接運(yùn)用在項(xiàng)目上煤搜,只需要在解碼圖像時(shí)bitmap畫布的bytesPerRow設(shè)為64的倍數(shù)即可免绿。


搜索CocoaChina微信公眾號:CocoaChina 微信掃一掃

訂閱每日移動(dòng)開發(fā)及APP推廣熱點(diǎn)資訊

公眾號:CocoaChina

我要投稿 ? 收藏文章 分享到:

16

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市擦盾,隨后出現(xiàn)的幾起案子嘲驾,更是在濱河造成了極大的恐慌,老刑警劉巖迹卢,帶你破解...
    沈念sama閱讀 211,265評論 6 490
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件辽故,死亡現(xiàn)場離奇詭異,居然都是意外死亡腐碱,警方通過查閱死者的電腦和手機(jī)誊垢,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,078評論 2 385
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來症见,“玉大人喂走,你說我怎么就攤上這事∧弊鳎” “怎么了芋肠?”我有些...
    開封第一講書人閱讀 156,852評論 0 347
  • 文/不壞的土叔 我叫張陵,是天一觀的道長遵蚜。 經(jīng)常有香客問我业栅,道長秒咐,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 56,408評論 1 283
  • 正文 為了忘掉前任碘裕,我火速辦了婚禮携取,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘帮孔。我一直安慰自己雷滋,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,445評論 5 384
  • 文/花漫 我一把揭開白布文兢。 她就那樣靜靜地躺著晤斩,像睡著了一般。 火紅的嫁衣襯著肌膚如雪姆坚。 梳的紋絲不亂的頭發(fā)上澳泵,一...
    開封第一講書人閱讀 49,772評論 1 290
  • 那天,我揣著相機(jī)與錄音兼呵,去河邊找鬼兔辅。 笑死,一個(gè)胖子當(dāng)著我的面吹牛击喂,可吹牛的內(nèi)容都是我干的维苔。 我是一名探鬼主播,決...
    沈念sama閱讀 38,921評論 3 406
  • 文/蒼蘭香墨 我猛地睜開眼懂昂,長吁一口氣:“原來是場噩夢啊……” “哼介时!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起凌彬,我...
    開封第一講書人閱讀 37,688評論 0 266
  • 序言:老撾萬榮一對情侶失蹤沸柔,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后铲敛,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體褐澎,經(jīng)...
    沈念sama閱讀 44,130評論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,467評論 2 325
  • 正文 我和宋清朗相戀三年原探,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了乱凿。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片顽素。...
    茶點(diǎn)故事閱讀 38,617評論 1 340
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡咽弦,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出胁出,到底是詐尸還是另有隱情型型,我是刑警寧澤,帶...
    沈念sama閱讀 34,276評論 4 329
  • 正文 年R本政府宣布全蝶,位于F島的核電站闹蒜,受9級特大地震影響寺枉,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜绷落,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,882評論 3 312
  • 文/蒙蒙 一姥闪、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧砌烁,春花似錦筐喳、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,740評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至管呵,卻和暖如春梳毙,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背捐下。 一陣腳步聲響...
    開封第一講書人閱讀 31,967評論 1 265
  • 我被黑心中介騙來泰國打工账锹, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人蔑担。 一個(gè)月前我還...
    沈念sama閱讀 46,315評論 2 360
  • 正文 我出身青樓牌废,卻偏偏與公主長得像,于是被迫代替她去往敵國和親啤握。 傳聞我的和親對象是個(gè)殘疾皇子鸟缕,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,486評論 2 348

推薦閱讀更多精彩內(nèi)容