iOS tableView多圖列表滑動時卡頓的優(yōu)化

FastImageCache是Path團隊開發(fā)的一個開源庫洁段,用于提升圖片的加載和渲染速度应狱,讓基于圖片的列表滑動起來更順暢,來看看它是怎么做的祠丝。

優(yōu)化點

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

  1. 從磁盤拷貝數(shù)據(jù)到內(nèi)核緩沖區(qū)
  2. 從內(nèi)核緩沖區(qū)復制數(shù)據(jù)到用戶空間
  3. 生成UIImageView,把圖像數(shù)據(jù)賦值給UIImageView
  4. 如果圖像數(shù)據(jù)為未解碼的PNG/JPG罐韩,解碼為位圖數(shù)據(jù)
  5. CATransaction捕獲到UIImageView layer樹的變化
  6. 主線程Runloop提交CATransaction憾赁,開始進行圖像渲染
    • 6.1 如果數(shù)據(jù)沒有字節(jié)對齊,Core Animation會再拷貝一份數(shù)據(jù)散吵,進行字節(jié)對齊。
    • 6.2 GPU處理位圖數(shù)據(jù)蟆肆,進行渲染矾睦。

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

  1. 使用mmap內(nèi)存映射,省去了上述第2步數(shù)據(jù)從內(nèi)核空間拷貝到用戶空間的操作炎功。
  2. 緩存解碼后的位圖數(shù)據(jù)到磁盤枚冗,下次從磁盤讀取時省去第4步解碼的操作。
  3. 生成字節(jié)對齊的數(shù)據(jù)蛇损,防止上述第6.1步CoreAnimation在渲染時再拷貝一份數(shù)據(jù)赁温。

接下來具體介紹這三個優(yōu)化點以及它的實現(xiàn)。

內(nèi)存映射

平常我們讀取磁盤上的一個文件淤齐,上層API調(diào)用到最后會使用系統(tǒng)方法read()讀取數(shù)據(jù)股囊,內(nèi)核把磁盤數(shù)據(jù)讀入內(nèi)核緩沖區(qū),用戶再從內(nèi)核緩沖區(qū)讀取數(shù)據(jù)復制到用戶內(nèi)存空間更啄,這里有一次內(nèi)存拷貝的時間消耗稚疹,并且讀取后整個文件數(shù)據(jù)就已經(jīng)存在于用戶內(nèi)存中,占用了進程的內(nèi)存空間祭务。

FastImageCache采用了另一種讀寫文件的方法内狗,就是用mmap把文件映射到用戶空間里的虛擬內(nèi)存,文件中的位置在虛擬內(nèi)存中有了對應的地址义锥,可以像操作內(nèi)存一樣操作這個文件柳沙,相當于已經(jīng)把整個文件放入內(nèi)存,但在真正使用到這些數(shù)據(jù)前卻不會消耗物理內(nèi)存拌倍,也不會有讀寫磁盤的操作赂鲤,只有真正使用這些數(shù)據(jù)時,也就是圖像準備渲染在屏幕上時贰拿,虛擬內(nèi)存管理系統(tǒng)VMS才根據(jù)缺頁加載的機制從磁盤加載對應的數(shù)據(jù)塊到物理內(nèi)存蛤袒,再進行渲染。這樣的文件讀寫文件方式少了數(shù)據(jù)從內(nèi)核緩存到用戶空間的拷貝膨更,效率很高妙真。

解碼圖像

一般我們使用的圖像是JPG/PNG,這些圖像數(shù)據(jù)不是位圖荚守,而是是經(jīng)過編碼壓縮后的數(shù)據(jù)珍德,使用它渲染到屏幕之前需要進行解碼轉(zhuǎn)成位圖數(shù)據(jù)练般,這個解碼操作是比較耗時的,并且沒有GPU硬解碼锈候,只能通過CPU薄料,iOS默認會在主線程對圖像進行解碼。很多庫都解決了圖像解碼的問題泵琳,不過由于解碼后的圖像太大摄职,一般不會緩存到磁盤,SDWebImage的做法是把解碼操作從主線程移到子線程获列,讓耗時的解碼操作不占用主線程的時間谷市。

FastImageCache也是在子線程解碼圖像,不同的是它會緩存解碼后的圖像到磁盤击孩。因為解碼后的圖像體積很大迫悠,F(xiàn)astImageCache對這些圖像數(shù)據(jù)做了系列緩存管理,詳見下文實現(xiàn)部分巩梢。另外緩存的圖像體積大也是使用內(nèi)存映射讀取文件的原因创泄,小文件使用內(nèi)存映射無優(yōu)勢,內(nèi)存拷貝的量少括蝠,拷貝后占用用戶內(nèi)存也不高鞠抑,文件越大內(nèi)存映射優(yōu)勢越大。

字節(jié)對齊

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

那什么是字節(jié)對齊呢,按我的理解礼烈,為了性能弧满,底層渲染圖像時不是一個像素一個像素渲染,而是一塊一塊渲染此熬,數(shù)據(jù)是一塊塊地取庭呜,就可能遇到這一塊連續(xù)的內(nèi)存數(shù)據(jù)里結尾的數(shù)據(jù)不是圖像的內(nèi)容,是內(nèi)存里其他的數(shù)據(jù)犀忱,可能越界讀取導致一些奇怪的東西混入募谎,所以在渲染之前CoreAnimation要把數(shù)據(jù)拷貝一份進行處理,確保每一塊都是圖像數(shù)據(jù)阴汇,對于不足一塊的數(shù)據(jù)置空数冬。大致圖示:(pixel是圖像像素數(shù)據(jù),data是內(nèi)存里其他數(shù)據(jù))

塊的大小應該是跟CPU cache line有關搀庶,ARMv7是32byte拐纱,A9是64byte铜异,在A9下CoreAnimation應該是按64byte作為一塊數(shù)據(jù)去讀取和渲染,讓圖像數(shù)據(jù)對齊64byte就可以避免CoreAnimation再拷貝一份數(shù)據(jù)進行修補秸架。FastImageCache做的字節(jié)對齊就是這個事情揍庄。

實現(xiàn)

FastImageCache把同個類型和尺寸的圖像都放在一個文件里,根據(jù)文件偏移取單張圖片东抹,類似web的css雪碧圖蚂子,這里稱為ImageTable。這樣做主要是為了方便統(tǒng)一管理圖片緩存缭黔,控制緩存的大小缆镣,整個FastImageCache就是在管理一個個ImageTable的數(shù)據(jù)。整體實現(xiàn)的數(shù)據(jù)結構如圖:

一些補充和說明:

ImageTable

  1. 一個ImageFormat對應一個ImageTable试浙,ImageFormat指定了ImageTable里圖像渲染格式/大小等信息,ImageTable里的圖像數(shù)據(jù)都由ImageFormat規(guī)定了統(tǒng)一的尺寸寞蚌,每張圖像大小都是一樣的田巴。
  2. 一個ImageTable一個實體文件,并有另一個文件保存這個ImageTable的meta信息挟秤。
  3. 圖像使用entityUUID作為唯一標示符壹哺,由用戶定義,通常是圖像url的hash值艘刚。ImageTable Meta的indexMap記錄了entityUUID->entryIndex的映射管宵,通過indexMap就可以用圖像的entityUUID找到緩存數(shù)據(jù)在ImageTable對應的位置。

ImageTableEntry

  1. ImageTable的實體數(shù)據(jù)是ImageTableEntry攀甚,每個entry有兩部分數(shù)據(jù)箩朴,一部分是對齊后的圖像數(shù)據(jù),另一部分是meta信息秋度,meta保存這張圖像的UUID和原圖UUID炸庞,用于校驗圖像數(shù)據(jù)的正確性。
  2. Entry數(shù)據(jù)是按內(nèi)存分頁大小對齊的荚斯,數(shù)據(jù)大小是內(nèi)存分頁大小的整數(shù)倍埠居,這樣可以保證虛擬內(nèi)存缺頁加載時使用最少的內(nèi)存頁加載一張圖像。
  3. 圖像數(shù)據(jù)做了字節(jié)對齊處理事期,CoreAnimation使用時無需再處理拷貝滥壕。具體做法是CGBitmapContextCreate創(chuàng)建位圖畫布時bytesPerRow參數(shù)傳64倍數(shù)。

Chunk

  • ImageTable和實體數(shù)據(jù)Entry間多了層Chunk兽泣,Chunk是邏輯上的數(shù)據(jù)劃分绎橘,N個Entry作為一個Chunk,內(nèi)存映射mmap操作是以chunk為單位的撞叨,每一個chunk執(zhí)行一次mmap把這個chunk的內(nèi)容映射到虛擬內(nèi)存金踪。為什么要多一層chunk呢浊洞,按我的理解,這樣做是為了靈活控制mmap的大小和調(diào)用次數(shù)胡岔,若對整個ImageTable執(zhí)行mmap法希,載入虛擬內(nèi)存的文件過大,若對每個Entry做mmap苫亦,調(diào)用次數(shù)會太多。

緩存管理

  • 用戶可以定義整個ImageTable里最大緩存的圖像數(shù)量诗眨,在有新圖像需要緩存時,如果緩存沒有超過限制巍膘,會以chunk為單位擴展文件大小,順序?qū)懴氯シ究怠H绻殉^最大緩存限制,會把最少使用的緩存替換掉,實現(xiàn)方法是每次使用圖像都會把UUID插入到MRUEntries數(shù)組的開頭箍邮,MRUEntries按最近使用順序排列了圖像UUID,數(shù)組里最后一個圖像就是最少使用的。被替換掉的圖片下次需要再使用時昨凡,再走一次取原圖—解壓—存儲的流程。

使用

FastImageCache適合用于tableView里緩存每個cell上同樣規(guī)格的圖像哪痰,優(yōu)點是能極大加快第一次從磁盤加載這些圖像的速度。但它有兩個明顯的缺點:一是占空間大。因為緩存了解碼后的位圖到磁盤惋啃,位圖是很大的健盒,寬高100100的圖像在2x的高清屏設備下就需要200200*4byte/pixel=156KB憨降,這也是為什么FastImageCache要大費周章限制緩存大小士嚎。二是接口不友好娇澎,需預定義好緩存的圖像尺寸伪很。FastImageCache無法像SDWebImage那樣無縫接入UIImageView辱匿,使用它需要配置ImageTable,定義好尺寸,手動提供的原圖,每種實體圖像要定義一個FICEntity模型,使邏輯變復雜繁疤。

FastImageCache已經(jīng)屬于極限優(yōu)化咖为,做圖像加載/渲染優(yōu)化時應該優(yōu)先考慮一些低代價高回報的優(yōu)化點躁染,例如CALayer代替UIImageVIew,減少GPU計算(去透明/像素對齊)叹放,圖像子線程解碼莉恼,避免Offscreen-Render等捶惜。在其他優(yōu)化都做到位田藐,圖像的渲染還是有性能問題的前提下才考慮使用FastImageCache進一步提升首次加載的性能,不過字節(jié)對齊的優(yōu)化倒是可以脫離FastImageCache直接運用在項目上,只需要在解碼圖像時bitmap畫布的bytesPerRow設為64的倍數(shù)即可景醇。

原文出處:http://blog.cnbang.net/tech/2578/

?著作權歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市吝岭,隨后出現(xiàn)的幾起案子三痰,更是在濱河造成了極大的恐慌,老刑警劉巖窜管,帶你破解...
    沈念sama閱讀 216,324評論 6 498
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件散劫,死亡現(xiàn)場離奇詭異,居然都是意外死亡幕帆,警方通過查閱死者的電腦和手機获搏,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,356評論 3 392
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來失乾,“玉大人颜凯,你說我怎么就攤上這事≌萄铮” “怎么了?”我有些...
    開封第一講書人閱讀 162,328評論 0 353
  • 文/不壞的土叔 我叫張陵蕾额,是天一觀的道長早芭。 經(jīng)常有香客問我,道長诅蝶,這世上最難降的妖魔是什么退个? 我笑而不...
    開封第一講書人閱讀 58,147評論 1 292
  • 正文 為了忘掉前任,我火速辦了婚禮调炬,結果婚禮上语盈,老公的妹妹穿的比我還像新娘。我一直安慰自己缰泡,他們只是感情好刀荒,可當我...
    茶點故事閱讀 67,160評論 6 388
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上篙程,一...
    開封第一講書人閱讀 51,115評論 1 296
  • 那天卤档,我揣著相機與錄音,去河邊找鬼藐俺。 笑死,一個胖子當著我的面吹牛,可吹牛的內(nèi)容都是我干的渠鸽。 我是一名探鬼主播,決...
    沈念sama閱讀 40,025評論 3 417
  • 文/蒼蘭香墨 我猛地睜開眼柴罐,長吁一口氣:“原來是場噩夢啊……” “哼徽缚!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起丽蝎,我...
    開封第一講書人閱讀 38,867評論 0 274
  • 序言:老撾萬榮一對情侶失蹤猎拨,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后屠阻,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體红省,經(jīng)...
    沈念sama閱讀 45,307評論 1 310
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,528評論 2 332
  • 正文 我和宋清朗相戀三年国觉,在試婚紗的時候發(fā)現(xiàn)自己被綠了吧恃。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 39,688評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡麻诀,死狀恐怖痕寓,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情蝇闭,我是刑警寧澤呻率,帶...
    沈念sama閱讀 35,409評論 5 343
  • 正文 年R本政府宣布,位于F島的核電站呻引,受9級特大地震影響礼仗,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜逻悠,卻給世界環(huán)境...
    茶點故事閱讀 41,001評論 3 325
  • 文/蒙蒙 一元践、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧童谒,春花似錦单旁、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,657評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽蔫饰。三九已至,卻和暖如春融柬,著一層夾襖步出監(jiān)牢的瞬間死嗦,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,811評論 1 268
  • 我被黑心中介騙來泰國打工粒氧, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留越除,地道東北人。 一個月前我還...
    沈念sama閱讀 47,685評論 2 368
  • 正文 我出身青樓外盯,卻偏偏與公主長得像摘盆,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子饱苟,可洞房花燭夜當晚...
    茶點故事閱讀 44,573評論 2 353

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