YYCache里值得學(xué)習(xí)的小細(xì)節(jié)

YYCache

大體結(jié)構(gòu)

NSCache

特性:

  • 線程安全
  • NSCache 底層并沒有用 NSDictionary 等已有的類,而是直接調(diào)用了 libcache.dylib,線程安全由 pthread_mutex 完成的。
  • 性能和 key 的相似度有關(guān),如果有大量相似的 key (比如 "1", "2", "3", ...) ,NSCache 的存取性能會(huì)劇烈下降,大量的時(shí)間被消耗在 CFStringEqual() 上
  • 收到內(nèi)存警告時(shí), 馬上清空所有緩存, 無法精準(zhǔn)控制

YYMemoryCache

特征:

  • LRU淘汰算法
  • 收到內(nèi)存警告時(shí)可精準(zhǔn)控制行為
  • 對(duì)象數(shù)量限制, 總?cè)萘肯拗? 存活時(shí)間限制
  • 查詢和插入時(shí), 并不是通過遍歷雙向鏈表, 而是使用字典hash表快速查找到指定結(jié)點(diǎn), 保證效率

主要手段:

  • CFMutableDictionaryRef來實(shí)現(xiàn)數(shù)據(jù)的實(shí)際存儲(chǔ);
  • 使用OSSpinLockLock來保證線程的安全 (之后改為pthread_mutex_lock)
  • 使用_YYLinkedMapNode和_YYLinkedMap來組織數(shù)據(jù)的存儲(chǔ)結(jié)構(gòu)
  • 使用YYMemoryCache來組織對(duì)數(shù)據(jù)的各種操作

性能對(duì)比 (QPS)

NSDictionary線程不安全, 表現(xiàn)最佳
YYCache表現(xiàn)優(yōu)于NSCache

性能對(duì)比

數(shù)據(jù)結(jié)構(gòu)的圖表 (LRU:Least Recently Used近期最少使用算法)

淘汰算法
數(shù)據(jù)結(jié)構(gòu)

Cache的操作共3種

  • 插入:當(dāng)Cache未滿時(shí),新的數(shù)據(jù)項(xiàng)只需插到雙鏈表頭部即可
  • 替換:當(dāng)Cache已滿時(shí),將新的數(shù)據(jù)項(xiàng)插到雙鏈表頭部,并刪除雙鏈表的尾結(jié)點(diǎn)即可
  • 查找:每次數(shù)據(jù)項(xiàng)被查詢到時(shí),都將此數(shù)據(jù)項(xiàng)移動(dòng)到鏈表頭部
鏈表

關(guān)于鎖的使用

用于內(nèi)存的鎖 (自旋鎖)

OSSpinLock 自旋鎖,性能最高的鎖。一直 do while 忙等。缺點(diǎn)是等待時(shí)消耗大量 CPU 資源,不適用于較長時(shí)間的任務(wù)。對(duì)于內(nèi)存緩存的存取來說,它非常合適。
根據(jù)大神博客, OSSPinLock有風(fēng)險(xiǎn), 蘋果的線程QoS會(huì)造成優(yōu)先級(jí)反轉(zhuǎn)
結(jié)論,除非開發(fā)者能保證訪問鎖的線程全部都處于同一優(yōu)先級(jí),否則 iOS 系統(tǒng)中所有類型的自旋鎖都不能再使用了。
舊版本

- (void)setObject:(id)object forKey:(id)key withCost:(NSUInteger)cost {
    OSSpinLockLock(&_lock);
    _YYLinkedMapNode *node = CFDictionaryGetValue(_lru->_dic, (__bridge const void *)(key));
      if (node) {
        //移動(dòng)到頭結(jié)點(diǎn)
        [_lru bringNodeToHead:node];
    } else {
        //添加到頭部
        [_lru insertNodeAtHead:node];
    }
    OSSpinLockUnlock(&_lock);
}
鎖性能對(duì)比

新版本

- (void)setObject:(id)object forKey:(id)key withCost:(NSUInteger)cost {
    pthread_mutex_lock(&_lock);
    _YYLinkedMapNode *node = CFDictionaryGetValue(_lru->_dic, (__bridge const void *)(key));
    if (node) {
        //移動(dòng)到頭結(jié)點(diǎn)
        [_lru bringNodeToHead:node];
    } else {
        //添加到頭部
        [_lru insertNodeAtHead:node];
    }
    pthread_mutex_unlock(&_lock);
}
用于磁盤的鎖 (互斥鎖)

dispatch_semaphore 信號(hào)量,

  • 沒有等待情況出現(xiàn)時(shí),性能比 pthread_mutex 還要高,但有等待情況時(shí),性能會(huì)下降許多
  • 相對(duì)于 OSSpinLock ,優(yōu)勢在于等待時(shí)不會(huì)消耗 CPU 資源。更適合耗時(shí)較大的磁盤IO

YYDiskCache

tricky的地方

  • 單條數(shù)據(jù)小于 20K 時(shí),SQLite讀取性能越高
  • 單條數(shù)據(jù)大于 20K 時(shí),寫文件更快

最佳實(shí)踐

  • SQLite 和文件存儲(chǔ)結(jié)合起來:key-value 元數(shù)據(jù)保存在 SQLite 中,而 value 數(shù)據(jù)則根據(jù)大小不同選擇 SQLite 或文件存儲(chǔ)。

結(jié)尾

自動(dòng)清理緩存

  • 為什么要指定在主線程清理緩存呢?
  • 這里為什么是pthread_mutex_trylock
- (void)_trimToCost:(NSUInteger)costLimit {
    /*
        costlimit為0時(shí), 直接清空所有并return
    */
    NSMutableArray *holder = [NSMutableArray new];
    BOOL finish = NO;

    while (!finish) {
        if (pthread_mutex_trylock(&_lock) == 0) {
            if (_lru->_totalCost > costLimit) {
                _YYLinkedMapNode *node = [_lru removeTailNode];
                if (node) [holder addObject:node];
            } else {
                finish = YES;
            }
            pthread_mutex_unlock(&_lock);
        } else {
            usleep(10 * 1000); //10 ms
        }
    }

    if (holder.count) {
        dispatch_queue_t queue = _lru->_releaseOnMainThread ? dispatch_get_main_queue() : YYMemoryCacheGetReleaseQueue();
        dispatch_async(queue, ^{
            [holder count]; // release in queue
        });
    }
}

更多關(guān)于鎖的問題

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容