[https://blog.ibireme.com/2015/10/26/yycache/]

iOS 开发中总会用到各种缓存,最初我是用的一些开源的缓存库,但到总觉得缺少某些功能,或某些 API 设计的不够好用。YYCache (https://github.com/ibireme/YYCache) 是我新造的一个轮子,下面说一下这个轮子的设计思路。

内存缓存

通常一个缓存是由内存缓存和磁盘缓存组成,内存缓存提供容量小但高速的存取功能,磁盘缓存提供大容量但低速的持久化存储。相对于磁盘缓存来说,内存缓存的设计要更简单些,下面是我调查的一些常见的内存缓存。

NSCache 是苹果提供的一个简单的内存缓存,它有着和 NSDictionary 类似的 API,不同点是它是线程安全的,并且不会 retain key。我在测试时发现了它的几个特点:NSCache 底层并没有用 NSDictionary 等已有的类,而是直接调用了 libcache.dylib,其中线程安全是由 pthread_mutex 完成的。另外,它的性能和 key 的相似度有关,如果有大量相似的 key (比如 “1”, “2”, “3”, …),NSCache 的存取性能会下降得非常厉害,大量的时间被消耗在 CFStringEqual() 上,不知这是不是 NSCache 本身设计的缺陷。

TMMemoryCache 是 TMCache 的内存缓存实现,最初由 Tumblr 开发,但现在已经不再维护了。TMMemoryCache 实现有很多 NSCache 并没有提供的功能,比如数量限制、总容量限制、存活时间限制、内存警告或应用退到后台时清空缓存等。TMMemoryCache 在设计时,主要目标是线程安全,它把所有读写操作都放到了同一个 concurrent queue 中,然后用 dispatch_barrier_async 来保证任务能顺序执行。它错误的用了大量异步 block 回调来实现存取功能,以至于产生了很大的性能和死锁问题。

PINMemoryCache 是 Tumblr 宣布不在维护 TMCache 后,由 Pinterest 维护和改进的一个内存缓存。它的功能和接口基本和 TMMemoryCache 一样,但修复了性能和死锁的问题。它同样也用 dispatch_semaphore 来保证线程安全,但去掉了dispatch_barrier_async,避免了线程切换带来的巨大开销,也避免了可能的死锁。

YYMemoryCache 是我开发的一个内存缓存,相对于 PINMemoryCache 来说,我去掉了异步访问的接口,尽量优化了同步访问的性能,用 OSSpinLock 来保证线程安全。另外,缓存内部用双向链表和 NSDictionary 实现了 LRU 淘汰算法,相对于上面几个算是一点进步吧。

下面的单线程的 Memory Cache 性能基准测试:

可以看到 YYMemoryCache 的性能不错,仅次于 NSDictionary + OSSpinLock;
NSCache 的写入性能稍差,读取性能不错;
PINMemoryCache 的读写性能也还可以,但读取速度差于 NSCache;
TMMemoryCache 性能太差以至于图上都看不出来了。

磁盘缓存

为了设计一个比较好的磁盘缓存,我调查了大量的开源库,包括 TMDiskCache、PINDiskCache、SDWebImage、FastImageCache 等,也调查了一些闭源的实现,包括 NSURLCache、Facebook 的 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 淘汰算法、更快的数据统计,更多的容量控制选项。

备注:

关于锁:

OSSpinLock 自旋锁,性能最高的锁。原理很简单,就是一直 do while 忙等。它的缺点是当等待时会消耗大量 CPU 资源,所以它不适用于较长时间的任务。对于内存缓存的存取来说,它非常合适。

dispatch_semaphore 是信号量,但当信号总量设为 1 时也可以当作锁来。在没有等待情况出现时,它的性能比 pthread_mutex 还要高,但一旦有等待情况出现时,性能就会下降许多。相对于 OSSpinLock 来说,它的优势在于等待时不会消耗 CPU 资源。对磁盘缓存来说,它比较合适。

关于 Realm:

Realm 是一个比较新的数据库,针对移动应用所设计。它的 API 对于开发者来说非常友好,比 SQLite、CoreData 要易用很多,但相对的坑也有不少。我在测试 SQLite 性能时,也尝试对它做了些简单的评测。我从 Realm 官网下载了它提供的 benchmark 项目,更新 SQLite 到官网最新的版本,并启用了 SQLite 的 sqlite3_stmt 缓存。我的评测结果显示 Realm 在写入性能上差于 SQLite,读取小数据时也差 SQLite 不少,读取较大数据时 Realm 有很大的优势。当然这只是我个人的评测,可能并不能反映真实项目中具体的使用情况。我想看看它的实现原理,但发现 Realm 的核心 realm-core 是闭源的(评论里 Realm 员工提到目前有在 Apache 2.0 授权下的开源计划),能知道的是 Realm 应该用 了 mmap 把文件映射到内存,所以才在较大数据读取时获得很高的性能。另外我注意到添加了 Realm 的 App 会在启动时向某几个 IP 发送数据,评论中有 Realm 员工反馈这是发送匿名统计数据,并且只针对模拟器和 Debug 模式。这部分代码目前是开源的,并且可以通过环境变量 REALM_DISABLE_ANALYTICS 来关闭,如果有使用 Realm 的可以注意一下。

YYCache 设计思路相关推荐

  1. BBWebImage 设计思路

    BBWebImage 设计思路 BBWebImage 是高性能 Swift 图片组件,用于图片下载.缓存.编解码.编辑与展示. GitHub 地址: https://github.com/Silenc ...

  2. 第 5 章 Nova - 025 - OpenStack 通用设计思路

    API 前端服务 每个 OpenStack 组件可能包含若干子服务,其中必定有一个 API 服务负责接收客户请求. 以 Nova 为例 nova-api 作为 Nova 组件对外的唯一窗口,向客户暴露 ...

  3. 亿级流量网关设计思路

    本文准备围绕七个点来讲网关,分别是网关的基本概念.网关设计思路.网关设计重点.流量网关.业务网关.常见网关对比,对基础概念熟悉的朋友可以根据目录查看自己感兴趣的部分. 什么是网关 网关,很多地方将网关 ...

  4. 传授“带权重的负载均衡实现算法”独家设计思路!

    作者|孙玄/陈东 分布式系统中,大部分系统调用都会涉及到负载均衡,例如:客户端发往服务端的请求首先到达反向代理,然后反向代理再通过负载均衡算法将请求转发到业务系统:或者后端业务系统各模块间的调用前,也 ...

  5. 会计期间在ERP开发中的设计思路

    会计期间在ERP开发中的设计思路 2010年08月03日00:00 it168网站原创  作者:IT168 阿峰  编辑:李倩 评论:1条 本文Tag: ERP ERP开发 系统分析 [IT168 专 ...

  6. “私有云”安全的“过渡”时期-“云朵”方案的设计思路

    一.私有云安全的尴尬现状 云计算因为能够提供虚拟化的资源池.弹性的服务能力.自助服务等,深得CIO们的青睐,为了提高企业IT设备的利用率,提高服务容灾的能力,提高对业务支撑的快速响应能力,大多数的企业 ...

  7. OpenStack 通用设计思路 - 每天5分钟玩转 OpenStack(25)

    API 前端服务 每个 OpenStack 组件可能包含若干子服务,其中必定有一个 API 服务负责接收客户请求. 以 Nova 为例,nova-api 作为 Nova 组件对外的唯一窗口,向客户暴露 ...

  8. 总体设计和登陆服务器 [游戏服务器的设计思路 转]

    作者博客: http://blog.csdn.net/yahle 大纲: 项目的历史背景 服务器的设计思路 服务器的技术 服务器的设计 服务器的改进 图形引擎myhoho及UI库的设计 客户端与服务器 ...

  9. Spark ShuffleManager内存缓冲器SortShuffleWriter设计思路剖析-Spark商业环境实战

    本套系列博客从真实商业环境抽取案例进行总结和分享,并给出Spark源码解读及商业实战指导,请持续关注本套博客.版权声明:本套Spark源码解读及商业实战归作者(秦凯新)所有,禁止转载,欢迎学习. Sp ...

最新文章

  1. shell中$后加引号有什么用($string和$'string')
  2. 编程之美 set 1 不要被阶乘吓倒
  3. 算法和时间复杂度概念(一级)
  4. C++ STL string的输出
  5. python参数传递方法_【python学习笔记】函数参数传递方法
  6. 悲哀!面试现场,简单几道java算法题,90%程序员没写出来
  7. 从Postman到ApiPost——码农闰土
  8. redhat 完全卸载mysql_Linux下彻底卸载mysql数据库
  9. 软考数据库系统工程师教材改版啦
  10. 离散数学在计算机相关领域的应用
  11. LPC1788 Fatfs读写U盘操作
  12. CRT软件光标不闪烁
  13. 华为小程序怎么弄出来_华为手机添加桌面小程序 华为手机小程序怎么弄出来...
  14. lucene.net和(pangu)盘古分词 搜索引擎的简单实现
  15. DL之RNN:人工智能为你写歌词(林夕写给陈奕迅)——基于TF利用RNN算法实现【机器为你作词】、训练测试过程全记录
  16. (以三星S8为例)安卓全面屏手势设置教程
  17. VMware连接U盘后无法显示U盘
  18. Ubuntu 20.04使用deepin wine安装Windows官方版QQ、微信、Tim等
  19. 如何检查并清除挖矿程序
  20. 怎样恢复被删除的文件

热门文章

  1. 计算机化系统验证管理 360,计算机化系统验证合集
  2. 动手制作一个可以手机APP控制的智能小夜灯
  3. lenovo thinkpad t460s opensuse linux 保护电池设置电池充电阀值...
  4. 【springcloudalibaba】naocs+dubbo,服务注册后没有提供者,无法注册No provider available from regist
  5. 机器学习笔记二十四 中文分词资料整理
  6. 网传京东37岁程序员在工位猝死。当事人:我还没死,还能加班!
  7. 栈内存与堆内存,堆内存的使用与理解。
  8. 基于智能手机的家校沟通系统设计与实现_kaic
  9. 《Java程序设计》期末复习100题(精讲)
  10. 在群晖synoboot.img引导文件中加入网卡驱动_ESXI6.7安装群晖详细教程