这篇文章是关于 Redis 的一个技术类小八卦,我看完直呼 666。
这文之前发过,不过没多少人看到,并且现在我的观点也发生了点变化,所以重新修改一波。
开始表演
我无意间查到一位叫 mattsta 的老哥的 14 年写的文章,这老哥说 Redis 现在实现的 CRC 算法太简陋了!他有能提升 4 倍性能的增强版 CRC 算法 - CRCSpeed!
我马上去看了我本地 Redis 5.0 版本的源码,发现 CRC 算法并没有采纳他的增强版,还是老的实现。
我又去看了最新 6.0 的版本,发现 CRC-64 改成了 CRCSpeed 的实现,在 2020 年 4 月 28 号提交的,提交者是 antirez,也就是 Redis 的作者。
这就让我越发的好奇了,这 2014 到 2020 跨度有点大啊,到底发生了啥?
![](https://img-blog.csdnimg.cn/img_convert/29b237ee7b4464e62692bb347b384d36.png)
然后我又去翻了 mattsta 的 Github ,追踪了整个事情发展的历史脉络,事情越来越有意思了。
我已经迫不及待地想和大家一起再来看一遍这哥们的文章,过一遍这件事。
这哥们的文章是英文的,不过重点我在图中都标红了,也都做了相应翻译。
Fancy CRCing You Here
这老哥文章标题就有那味儿!Fancy CRCing You Here
,这是要开炮了。
然后这老哥文章的第一句话就很为人着想。
![](https://img-blog.csdnimg.cn/img_convert/a768fbdf11474fe304f37b862b993349.png)
简单翻译下就是 short 的人直接点链接看结果,那我肯定不 short 的啊,所以我选择接下去看。
紧接着这老哥就抛出了以下的话:
![](https://img-blog.csdnimg.cn/img_convert/b6f75c7e25fc8893997d166ff98ffbd6.png)
Redis 的 CRC-64 的实现有很多人拷贝到他们的项目中使用,CRC-16 也有少量拷贝。这算法是能用啊,但是可以有更好的实现。
当时我搜了下, Github 上有 2353 个项目用到了 Redis 的 CRC-64 实现,325 个项目用到了 Redis 的 CRC-16 实现。
![](https://img-blog.csdnimg.cn/img_convert/e5d1f159d56c6c40acb1b1cb319888aa.png)
这位老哥为他们感到惋惜,他说:唉,他们值得更好的CRC算法呀。
紧接着老哥开启了第一波输出。
![](https://img-blog.csdnimg.cn/img_convert/67d1baecd85468abf3f1485284dc0bec.png)
简单翻一下:
Redis 决定忽略吞吐量和延迟方面的提高,因为他们更喜欢像1999年那样编码。
代价就是他们过时的传统设计慢了40倍。大家干得漂亮啊。(不知道这老哥是笔误还是故意把4倍在开头写成40倍的,我就揣测下:),那个outdated legacy design超链接跳过去就是标注着 antirez 写的 CRC-64 的实现,直接粗暴,我很喜欢)。
哎,如果现在有人写了一个代替 redis 和 memcached 的实现就好了啊。
这正面刚了一波,但是还没完,老哥紧接着开始放招。
What’s Wrong
他先说了下 CRC 本质上是无法并行的,因为下一次迭代的值取决于前一次迭代。然后又指出了 Redis 中的哪几个地方使用到 CRC。
![](https://img-blog.csdnimg.cn/img_convert/2f721359a5e478dd849e1d0985318aca.png)
CRC-64 用在了三个地方:
在跨实例迁移键时添加校验和,(并验证上述校验和)
为 RDB 输出添加一个校验和,用于复制和持久化(是可选项,可通过配置禁用,因为性能低)
用于内存测试
CRC-16 用在了一个地方:
作为集群中分配集群槽的键的散列函数
mattsta 接着放招:
![](https://img-blog.csdnimg.cn/img_convert/5363179db7d1853f31ef473de4f47aa1.png)
简单翻译下:这 Redis 的实现极其简单(这个 extremely
单词我很是喜欢),就是一个简单的查表法,然后循环一个字节一个字节比过去,时间复杂度是O(N)。
先踩一波,然后抛出好的解决办法,啧啧老哥深谙此道。
What’s Better
![](https://img-blog.csdnimg.cn/img_convert/932f5cd18a0b5cfc08bb3e7f9f5e1e41.png)
简单翻译下:我在网上乱翻了一番,想看看其他人是如何实现 CRC-64 的。但是大部分是拷贝 Redis 的(哎,恨其不争)。
然后 mattsta 发现了 stackoverflow 有个叫 Mark 的哥们自己写了个高速版 CRC-64 实现,他将 Mark 的实现和 Redis 的实现进行了对比,发现 Mark 的版本比 Redis 版本快了400% ,分别是1.6 GB/s 和400 MB/s。
![](https://img-blog.csdnimg.cn/img_convert/59e82f6567532b7bc47b5dffe578f79e.png)
但是!Mark 和 Redis 实现的 CRC-64 属于不同的版本,没错 CRC-64 算法有很多变体。
于是 mattsta 把枪口暂时对准了 CRC,没错他喷了一波 CRC 算法本身。
![](https://img-blog.csdnimg.cn/img_convert/4d911baeb6712e5bc8441b9af6475baa.png)
原谅我的孤陋寡闻,我一直以为 pretty 和 girl 比较配,原来还能用来和 awful搭,我学到了,嗯。pretty fu..。
然后 mattsta 说我们不能直接用 Mark 的实现,但是我们可以看看是 Mark 的实现。
What’s Improved
mattsta 先展示了下 Redis 的实现,就是每个字节循环操作,然后查表。
![](https://img-blog.csdnimg.cn/img_convert/6c1c1ec886e4877739235c2a53b8e261.png)
然后他又贴出了快版本的实现。
![](https://img-blog.csdnimg.cn/img_convert/1749e72a5a82033b0d2055ac14bd1b99.png)
可以看到也是查表法,不过一次不是处理 1 个字节而是 8 个字节。
![](https://img-blog.csdnimg.cn/img_convert/092d2b85370d8572c641d300da6300c8.png)
mattsta 用了一个我觉得很搞笑的形容 tiger prepping to devour your entrails
。这新版本的代码看起来像一只老虎准备吃掉你的内脏!再坚持一会儿!这还是个超链接,我点过去真的是一只老虎图!
![](https://img-blog.csdnimg.cn/img_convert/0f98af4c34bc6ffb92d0107b57690cb0.png)
哈哈哈,容我先笑一会儿,这老哥真有意思!
然后他说强调了下这个算法快的原因,就是利用多维数组查表,每次循环可以一次处理 8 个字节。
![](https://img-blog.csdnimg.cn/img_convert/10e03e4e8a78fb64d129afd9b3d698bc.png)
因此对于 500 MB 的输入来说 Redis 的版本需要 5 亿次循环,而新版本只要 6250 万次。
![](https://img-blog.csdnimg.cn/img_convert/792b58c80db64f927c1122a7002c51a3.png)
这个slicing-by-8
是英特尔公司的研究人员于 2006 年发布,也就是说利用 8 个查找表,每个查找表包含另一个 256 字节的查找表来实现一次能处理 8 个字节的 CRC-64 算法。
简单地说,就是空间换时间的操作。
![](https://img-blog.csdnimg.cn/img_convert/b484fb47c5457daae95b9ab4feebe1b0.png)
于是,这位老哥找到了灵感,他要自己实现一个和 Redis 匹配的 CRC 算法。
Result
![](https://img-blog.csdnimg.cn/img_convert/febce2e81e48697229df7803fa487a52.png)
简单翻译下:就是经过一年的努力,mattsta 终于做出符合 Redis 匹配的 CRC-64 算法快速版本,而且作为额外奖励,也能用在CRC-16上噢,并且可以摒弃老版本源代码中一堆静态查找表。
可以在需要的时候再动态生成,而不是总是拖着它们使代码膨胀。
我们来看看老版本的代码确实有一堆,我截了一小段。
![](https://img-blog.csdnimg.cn/img_convert/8967763499a2f16d11ec324132ca05d1.png)
也就是 mattsta 写的快速 CRC 实现版本-crcspeed,不仅速度更快,而且清减了代码。
然后 mattsta 来了一波不要相信我说的话,咱们来让数据说话(傲娇.jpg)。
![](https://img-blog.csdnimg.cn/img_convert/043c8afd42d593a4c2c4d8d732714838.png)
![](https://img-blog.csdnimg.cn/img_convert/0b7af144c14a8d88414aefdd6c8305a0.png)
通过 mattsta 自己的笔记本测出的 crcspeed 从耗时、吞吐量和每字节所需CPU周期三方面来看都优于 Redis 的实现。
Real-World Impact
mattsta 又指出 crcspeed 能给Redis 带来啥呢?
![](https://img-blog.csdnimg.cn/img_convert/75b95071162bc92c0f2fc93130c26ca0.png)
简单翻译下:Redis 在生成 RDB 的时候会 fork 出子进程,因此采用的是写时复制,所以内存的增长取决于写入的负载,那么快速的结束 RDB 退出 fork 的子进程,用在 COW 的内存就会更少,而生成 RDB 的时候又用到了 CRC-64 作为校验,那么 CRC-64 校验越快,RDB 生成的就越快,用于 COW 复制而使用的内存就越少。
并且
![](https://img-blog.csdnimg.cn/img_convert/5bec13fe3b8e61215fd13f3535eb6d32.png)
CRC-16来映射槽的时候,如果用户正在做一些古怪的事情,比如使用 300 MB 的键,那么快速的 CRC-16 可以减少 400% 的集群槽分配开销!
这个If users are doing wacky things
,我很是赞同,不管公司内部还是公司外部,你所实现的接口都要怀揣着使用者会以最大的恶意去调用来预防。
![](https://img-blog.csdnimg.cn/img_convert/b042e1da81ad0d9fe7fcae3cb0da0a08.png)
mattsta 说这是一个有效和高效的多方面的双赢!
我本以为这位老哥的文章写到这里就差不多了,然而并没有。
Minor Notes
![](https://img-blog.csdnimg.cn/img_convert/6ce03bae4aa9b4c2afa7abca778cd8a7.png)
可以看出 mattsta 是不想造轮子的,但是实在是没有轮子啊!于是他只能自己实现一个,这是个新轮子!
Resources Consulted
然后他列出了他所参考的一些资源,他首先感谢了「A PAINLESS GUIDE TO CRC ERROR DETECTION ALGORITHMS」这篇文章。
让我们学一下感谢参考资料的正确姿势。
![](https://img-blog.csdnimg.cn/img_convert/7320a30d82d5bbf5dbd7ceb225cf8770.png)
看到没,这才是感谢的正确姿势!我们来看下它所感谢的文章的样子。
![](https://img-blog.csdnimg.cn/img_convert/2109366cc9b78cb469967898f3f973c4.png)
老哥说:有一说一,确实,纯路人,身为一个 txt,编写良好、格式良好,有趣。在风格、布局和语气的所有方面都经过了专家的深思熟虑。
夸了一番之后, mattsta 觉得还不够,还得加一点自己的想法。
![](https://img-blog.csdnimg.cn/img_convert/2d7c1cb0c8796af5c8eb9f5c62856889.png)
简单翻一下:在某种程度上,互联网已经失去了保存写的好的、格式良好的、信息丰富的指南以及常见问题的解答和平易近人的研究论文的能力。我们应该努力把那部分世界夺回来。这种损失该归咎于什么呢?对风格的过分依赖?CSS?还是 JavaScript?PHP?。
世界上最好的语言警告!
![](https://img-blog.csdnimg.cn/img_convert/81bf3c389fb356996b733225a10b0902.png)
看到没,这才是感谢的正确打开方式,我学废了。mattsta 老哥追求纯干货,说,别给我整一些花里胡哨的!
而且这篇文章还让 mattsta 确信他没有能力实现一个 CRC-64 算法,因此实际上他是依靠 pycrc 来实现的。
然后这位老哥又说 yahyahyah,linux kernel 也是这样用的,yahyahyah。
![](https://img-blog.csdnimg.cn/img_convert/62215e11d562de7eb51f4947e9eae5da.png)
老哥还找到一篇介绍 slicing by 8
的放在英特尔网上的论文,不过现在已经没了。所以先嘲讽了一波英特尔,还顺带还吐槽了傻*谷歌代码,每次打开这个 pdf 都自动下载,而不是在线浏览。
![](https://img-blog.csdnimg.cn/img_convert/e227ba2d87af6b35f3c3b4cfe1e9052c.png)
暴躁老哥,真的对我胃口哈哈哈。
mattsta 老哥写的文章还没结束,后面是关于实现的细节,我就不再继续翻译下去了,文末会放原文链接,有兴趣的朋友到时候可以看看。
我们再来看身为 Redis Contributor 为何 mattsta 会写这一篇文章?不应该提 pr 直接解决吗?难道这算法有什么致命之处使得 Antirez 不接受?
我们来追踪一下!
追踪事情的来龙去脉
首先这篇文章写于 2014-12-22
![](https://img-blog.csdnimg.cn/img_convert/d67634b296bddb84964fe49bb92d7a0b.png)
mattsta 在 2013-12 开始了对 redis.io 的第一次提交。
![](https://img-blog.csdnimg.cn/img_convert/12aea5d8662443f763e04af3b251a72c.png)
随后 mattsta 就开始了对 Redis 的输出,在 2014-04-01,提出了相关的 issue,并且附上了自己的对比。
![](https://img-blog.csdnimg.cn/img_convert/8b8dd8d08fd49c4064833a07c45b0663.png)
提出的 issue 没有受到团队成员的响应,寂寞如雪,只有一位金毛小哥,为其打 call,这个issue 此时还是 open 的。
然后在当年,2014-11-23,mattsta 创建了 crcspeed 库,并且提交了实现。
![](https://img-blog.csdnimg.cn/img_convert/510f822da2019b1295ebb9b445d2dc6f.png)
并在 2014-12-22 提交了pr,竟然和写文章是同一天!而且是先写了文章再提的 pr。
一开始我以为是提了 pr 迟迟得不到采纳,然后才一怒之下写的文章。
![](https://img-blog.csdnimg.cn/img_convert/b961dfa7455634ab16347ee922ecbb7c.png)
可以看到隔了一天有团队人员回应了,他说:我不知道这是否会被合并(我认为它应该) ,但是,该死的!这是一个伟大的提升!牛皮克拉斯!
2014-12-23,mattsta 又对 pr 做了一些补充说明,然而 antirez 还是没有回应。
直到 2015-01-10,mattsta 对 pr 又做了一波更新。
![](https://img-blog.csdnimg.cn/img_convert/db2cd2d94b298b41050fed7cd47c7931.png)
终于在 2015-02-25 号,等到了 antirez 的回复。
![](https://img-blog.csdnimg.cn/img_convert/7efba72c859209f1657e26475bb598ba.png)
antirez 说,很有意思,但是我想看到固定的大于 5% 收益的可重现的测试用例,即使是综合性的测试也没事,只要明显的能在 Redis 中反映出来即可,我相信从集群的 crc16 入手测试能很简单的证明效果,现在对于合并更快的实现不是很急,不过如果有一天你完成了这样的测试,我将会很感激。
然后给这个 pr 加了个标 review - and - merge
。
还加了个ps: 通常来说证明一个东西的性能提升是很重要啊,我在这里做了个例外,因为我看它单独的测试确实快了很多,我相信即使 Redis 没有使用上这个经验,但是迟早我们也会受益于它。
简单地说就是:mattsta 啊,你得搞个 Redis 相关的测试来证明它真的使得 Redis 性能提升了啊,这样我才能合并啊,不过我做个例外,是认可你这个的,给你打个标!(但是没有真正的合并)。
也就是说 antirez 其实是认可 mattsta 的实现的,但是 mattsta 没有给出和 Redis 相关的测试,所以还不能合并这个 pr。
这个 pr 就到这里过了,再也没有更新,也还是 Open 的。
mattsta 也没有继续说啥,对 Redis 输出到 2015 年初之后就不再输出了。
而 CRC-16 到现在还使用的是老版本,CRC-64 是 antirez 在时隔六年的2020-04-28做的修改,使用的就是 mattsta 的crcspeed。
再回头看
可以到 mattsta 在 2014-04-01 就提了 issue,然后没有任何回应的情况下自己研究,找了许多资料,最后实现了 crcspeed,也肝出了一篇文章,之后在同一天提了 PR,然后过了近两个月的时间得到 antirez 的回复,由其没有关于 Redis 的实质上的测试,因此不给合并,但被给予肯定。
我个人猜测 mattsta 可能还是有点生气的,这么一个通用的东西,我都给了横向对比测试了!这原理我也分析的这么清楚了!这明摆着肯定是 ok 的,你还要我测试啥!不合并拉倒!(再次傲娇.jpg)。
而 antirez 所在的角度不一样,他是 Redis 的亲爸爸。你说的没错,我认可你,但是你得拿出实质性的证明给我看看你帮我的 Redis 提升了多少,即使我知道这玩意肯定是 ok 的。
其实双方我都能理解,所处角色不同,我只能说如果我是 mattsta 我可能会在心里骂 antirez,如果我是 antirez 我觉得就该这样。
来张 mattsta 老哥的靓照,哈哈头发很多!
![](https://img-blog.csdnimg.cn/img_convert/8cc2c0afe675276966f70cb7fa166d33.png)
这篇文章讲述的就是这么个事儿。
我就是带着八卦之心来看为何身为 Contributor 的 mattsta 提的明显正确的 pr 没有被 merge,至于什么 CRC 的我不关心哈哈哈哈。
mattsta 老哥的钻研之心值得我们学习,当然还有他那搞笑的形容和五彩斑斓的感谢
参考链接
https://matt.sh/redis-crcspeed
https://github.com/mattsta?tab=repositories