iOS高性能的緩存框架:YYCache

jopen 10年前發布 | 104K 次閱讀 YYCache iOS開發 移動開發

iOS高性能的緩存框架

內存緩存

通常一個緩存是由內存緩存和磁盤緩存組成,內存緩存提供容量小但高速的存取功能,磁盤緩存提供大容量但低速的持久化存儲。相對于磁盤緩存來說,內存緩存的設計要更簡單些,下面是我調查的一些常見的內存緩存。

NSCache 是蘋果提供的一個簡單的內存緩存,它有著和 NSDictionary 類似的 API,不同點是它是線程安全的,并且不會 retain key。我在測試時發現了它的幾個特點:NSCache 底層并沒有用 NSDictionary 等已有的類,而是直接調用了 libcache.dylib,其中線程安全是由 pthread_mutex 完成的。另外,它的性能和 key 的相似度有關,如果有大量相似的 key (比如 "1", "2", "3", ...),NSCache 的存取性能會下降得非常厲害,大量的時間被消耗在 CFStringEqual() 上,不知這是不是 NSCache 本身設計的缺陷。

TMMemoryCacheTMCache 的內存緩存實現,最初由 Tumblr 開發,但現在已經不再維護了。TMMemoryCache 實現有很多 NSCache 并沒有提供的功能,比如數量限制、總容量限制、存活時間限制、內存警告或應用退到后臺時清空緩存等。TMMemoryCache 在設計時,主要目標是線程安全,它把所有讀寫操作都放到了同一個 serial queue 中,然后用 dispatch_semaphore 來保證最多只有一個線程訪問 queue。它錯誤的用了大量異步 block 回調來實現存取功能,以至于產生了很大的性能和死鎖問題。

PINMemoryCache 是 Tumblr 宣布不在維護 TMCache 后,由 Pinterest 維護和改進的一個內存緩存。它的功能和接口基本和 TMMemoryCache 一樣,但修復了性能和死鎖的問題。它同樣也用 dispatch_semaphore 來保證線程安全,但去掉了 serial queue,避免了線程切換帶來的巨大開銷,也避免了可能的死鎖。

YYMemoryCache 是我開發的一個內存緩存,相對于 PINMemoryCache 來說,我去掉了異步訪問的接口,盡量優化了同步訪問的性能,用 OSSpinLock 來保證性能安全。另外,緩存內部用雙向鏈表和 NSDictionary 實現了 LRU 淘汰算法,相對于上面幾個算是一點進步吧。

下面的單線程的 Memory Cache 性能基準測試:

iOS高性能的緩存框架:YYCache

可以看到 YYMemoryCache 的性能不錯,僅次于 NSDictionary + OSSpinLock;
NSCache 的寫入性能稍差,讀取性能不錯;
PINMemoryCache 的讀寫性能也還可以,但讀取速度差于 NSCache;
TMMemoryCache 性能太差以至于圖上都看不出來了。

磁盤緩存

為了設計一個比較好的磁盤緩存,我調查了大量的開源庫,包括 TMDiskCache、PINDiskCache、SDWebImage、FastImageCache 等,也調查了一些閉源的實現,包括 NSURLCache、非死book 的 FBDiskCache 等。他們的實現技術大致分為三類:基于文件讀寫、基于 mmap 文件內存映射、基于數據庫。

TMDiskCache, PINDiskCache, SDWebImage 等緩存,都是基于文件系統的,即一個 Value 對應一個文件,通過文件讀寫來緩存數據。他們的實現都比較簡單,性能也都相近,缺點也是同樣的:不方便擴展、沒有元數據、難以實現較好的淘汰算法、數據統 計緩慢。

FastImageCache 采用的是 mmap 將文件映射到內存。用過 MongoDB 的人應該很熟悉 mmap 的缺陷:熱數據的文件不要超過物理內存大小,不然 mmap 會導致內存交換嚴重降低性能;另外內存中的數據是定時 flush 到文件的,如果數據還未同步時程序掛掉,就會導致數據錯誤。拋開這些缺陷來說,mmap 性能非常高。

NSURLCache、FBDiskCache 都是基于 SQLite 數據庫的。基于數據庫的緩存可以很好的支持元數據、擴展方便、數據統計速度快,也很容易實現 LRU 或其他淘汰算法,唯一不確定的就是數據庫讀寫的性能,為此我評測了一下 SQLite 在真機上的表現。iPhone 6 64G 下,SQLite 寫入性能比直接寫文件要高,但讀取性能取決于數據大小:當單條數據小于 20K 時,數據越小 SQLite 讀取性能越高;單條數據大于 20K 時,直接寫為文件速度會更快一些。這和 SQLite 官網的描述基本一致。另 外,直接從官網下載最新的 SQLite 源碼編譯,會比 iOS 系統自帶的 sqlite3.dylib 性能要高很多。基于 SQLite 的這種表現,磁盤緩存最好是把 SQLite 和文件存儲結合起來:key-value 元數據保存在 SQLite 中,而 value 數據則根據大小不同選擇 SQLite 或文件存儲。NSURLCache 選定的數據大小的閾值是 16K;FBDiskCache 則把所有 value 數據都保存成了文件。

我的 YYDiskCache 也是采用的 SQLite 配合文件的存儲方式,在 iPhone 6 64G 上的性能基準測試結果見下圖。在存取小數據 (NSNumber) 時,YYDiskCache 的性能遠遠高出基于文件存儲的庫;而較大數據的存取性能則比較接近了。但得益于 SQLite 存儲的元數據,YYDiskCache 實現了 LRU 淘汰算法、更快的數據統計,更多的容量控制選項。

iOS高性能的緩存框架:YYCache

備注:

關于鎖:

OSSpinLock 自旋鎖,性能最高的鎖。原理很簡單,就是一直 do while 忙等。它的缺點是當等待時會消耗大量 CPU 資源,所以它不適用于較長時間的任務。對于內存緩存的存取來說,它非常合適。

dispatch_semaphore 是信號量,但當信號總量設為 1 時也可以當作鎖來。在沒有等待情況出現時,它的性能比 pthread_mutex 還要高,但一旦有等待情況出現時,性能就會下降許多。相對于 OSSpinLock 來說,它的優勢在于等待時不會消耗 CPU 資源。對磁盤緩存來說,它比較合適。

關于 Realm:

Realm 是一個比較新的數據庫,號稱是針對移動應用所設計。我在測試 SQLite 性能時,也嘗試對它做了些簡單的評測。我從 Realm 官網下載了它提供的 benchmark 項目,更新 SQLite 到官網最新的版本,并啟用了 SQLite 的 sqlite3_stmt 緩存。評測結果顯示 Realm 在寫入性能上差 SQLite 很多,讀取小數據時也差 SQLite 不少,只有讀取較大數據時 Realm 才有很大的優勢。我想看看它的實現原理,但發現 Realm 的核心 realm-core 是閉源的(還有跡象未來要收費),能知道的是 Realm 應該用 了 mmap 把文件映射到內存,所以才在較大數據讀取時獲得很高的性能。另外我注意到添加了 Realm 的 App 會在啟動時向某幾個 IP 發送數據,所以我強烈建議大家不要用 Realm。

https://github.com/ibireme/YYCache

 本文由用戶 jopen 自行上傳分享,僅供網友學習交流。所有權歸原作者,若您的權利被侵害,請聯系管理員。
 轉載本站原創文章,請注明出處,并保留原始鏈接、圖片水印。
 本站是一個以用戶分享為主的開源技術平臺,歡迎各類分享!