YYCache之《三》 内存缓存

前两篇中,介绍了YYCache中的Disk Cache, 本篇介绍一下Memory Cache.

业内地位

作者在调研对比了TMMemoryCache、PINMemoryCache、NSCache、NSDictionary、NSDictionary+Lock多钟方式之后,取精去粕,实现了自己的YYMemoryCache。
我把它归纳一下:


image

可见,YYMemoryCache基本上在性能上仅次于系统原始字典api,优于系统Cache、TM、PIN。但系统原始字典只是一个最通用的hashtable,并不能直接用做cache.所以YYMemoryCache是当前最好的内存缓存。
有一张基准性能对比表:


image

技术关键点

访问:CFDictionaryRef
操作:双向链表
淘汰:从大小、数量、使用时长三个维度自定义阈值去淘汰缓存
管理:可配置内存警告或退到后台是否清空缓存;是否自动校验阈值淘汰;释放对象是否异步,是否在主线程;
兼容:和NSCache API基本一致,线程安全

实现

主要流程如下:


image

1、Cache对象维护一个链表,一个字典。链表使修改删除的时间复杂度为O(1), 字典使查找的时间复杂度为O(1)。
2、用了pthread_mutex_t的锁,作者最开始(2015年)用的是自旋锁OSSpinLock,后来大家爆出自旋锁会在一定的时机出现死锁,比如持有锁的一方依赖等待锁的一方,由于没有优先级,谁都不进入睡眠,所以就死锁了。更多调研参考
自旋锁问题
作者之后(2016年)的做法就是替换为pthread_mutex_t,现在苹果引入了不公平锁os_unfair_lock,效率是要比pthread_mutex_t高一些的,也可以满足线程优先级。其实这里可以做一点优化。
3、一些配置,大小、数量、时长、清除时机,轮询监测。这些比较简单,大家感兴趣可以自己读一下源码。值得说的一点是,我对作者的默认每5秒监测一次(当然可以配置)持保留意见。轮询唤醒CPU的做法是非常耗电的,内存缓存在应用中一般会是一个单例,生命周期贯穿整个app,这样的操作可以优化到具体操作过程中。不建议使用这个默认轮询。可以在设置值的时候进行调整。
4、作者在一些细节方面的处理很值得人敬佩和学习。比如淘汰对象的释放,他支持了异步。作者认为大量对象的释放也是一笔不小的开销,会对主线程造成影响。处理的也比较巧

if (_lru->_releaseAsynchronously) {
            dispatch_queue_t queue = _lru->_releaseOnMainThread ? dispatch_get_main_queue() : YYMemoryCacheGetReleaseQueue();
            dispatch_async(queue, ^{
                [node class]; //hold and release in queue
            });
        }

YYMemoryCache之API

image

与NSCache非常类似

One more thing

之前讲过的YYDiskCache中,存储对象可以存于sqlite和file中,作者给出的阈值是20k,但没有给出具体测试的案例,经查询,sqlite官网中给出的结论是100k,所以这块有待研究和讨论。https://www.sqlite.org/intern-v-extern-blob.html

image

总结

1、YYCache(包括Memory和Disk)在当前业内非常优秀,可以满足几乎所有的缓存需求。
2、作者受年代的影响(2016年),锁的选择这块还可以略微优化,用上苹果最新的不公平锁。
3、对于APP电量消耗的考虑,不建议开启自动轮询trim功能,而是将监测时机放于添加修改时,以及一些系统时机,比如内存预警,后切前,前切后等。
4、对db与file阈值这件事,不确定到底是20k还是100k,有时间可以自己测试一下。
5、祝YY大神身体健康,一切顺利~

YYCache之《二》磁盘缓存的实现原理
YYCache之《一》磁盘缓存的简单使用

你可能感兴趣的:(YYCache之《三》 内存缓存)