大家好,我是 yes。

昨天表弟说有个学妹问他 Redis 为什么要用 CRC16(key) mod 16384 来计算 key 所处槽的位置,我想这 CRC 一般都是用来校验的,通过多项式转换成二进制再通过模2除法得到余数,这里用来做个 Hash 函数好像用的也没啥毛病(对于CRC不太了解的同学可以先去查查)。

于是我就去查,看看 Redis 的作者 antirez 有没有相关的介绍,这一查就查到了一位叫 mattsta 的老哥的 14 年写的文章,这老哥的文章可太有意思了,他认为 Redis 现在实现的 CRC 算法太简陋了,他有能提升 4 倍性能的增强版 CRC 算法 - CRCSpeed,因此他写了这篇文章进行了一波分析。

看完之后我马上去看了我本地 5.0 版本的源码,发现 CRC 算法并没有采纳他的增强版,还是老的实现。我又去看了最新 6.0 的版本,发现 CRC-64 改成了 CRCSpeed 的实现,在2020年4月28号提交的,提交者是 antirez,这就让我越发的好奇了,这2014到2020跨度有点大啊,到底发生了啥?

然后我又去看 mattsta 的 Github ,他还是 Redis 的 Contributor,于是我追踪了整个事情发展的历史脉络,事情越来越有意思了。我已经迫不及待地想和大家一起再来看一遍这哥们的文章,过一遍这件事。

不过我先到感谢一下我的表弟,不然我都没机会看到这篇文章,来表弟给大家打个招呼!

“大家好!我是表弟”。 好了表弟你可以走了。

表弟骂骂咧咧的退出群聊:“工具人就是工具人”。

接下来我就要用我蹩脚的英语来翻译了,如有纰漏敬请指正。

Fancy CRCing You Here

这老哥文章标题就有那味儿! Fancy CRCing You Here,我还特意去找了我专八同学来翻译一下。

然后这老哥文章的第一句话就很为人着想。

简单的翻译就是 short 的人直接点链接看结果,那我肯定不 short 的啊,紧接着这老哥就抛出了以下的话:

Redis 的 CRC-64 的实现有很多人拷贝到他们的项目中使用,CRC-16 也有少量拷贝。这算法是能用啊,但是可以有更好的实现。

我看了下 Github 上有 2353 个项目用到了 Redis 的 CRC-64 实现,325 个项目用到了 Redis 的 CRC-16 实现。

这位老哥为他们感到惋惜,唉他们值得更好的CRC算法呀。紧接着老哥开启了第一波输出。

简单翻一下就是:

  • Redis 决定忽略吞吐量和延迟方面的提高,因为他们更喜欢像1999年那样编码

  • 代价就是他们过时的传统设计慢了40倍。大家干得漂亮啊。(不知道这老哥是笔误还是故意把4倍在开头写成40倍的,我就揣测下:),那个超链接跳过去就是标注着 antirez 写的 CRC-64 的实现,粗暴直接,我很喜欢)。

  • ,如果现在有人写了一个代替 redis 和 memcached 的实现就好了啊。

然后老哥就开始放招。

What’s Wrong

他先说了下 CRC 本质上是无法并行的,因为下一次迭代的值取决于前一次迭代。然后又指出了 Redis 中的哪几个地方使用到 CRC。

CRC-64 用在了三个地方:

  • 在跨实例迁移键时添加校验和,(并验证上述校验和)

  • 为 RDB 输出添加一个校验和,用于复制和持久化(是可选项,可通过配置禁用,因为性能低)

  • 用于内存测试

CRC-16 用在了一个地方:

  • 作为集群中分配集群槽的键的散列函数

mattsta 接着放招:

简单翻译下:这 Redis 的实现极其简单(这个 extremely 单词我很是喜欢)。就是一个简单的查表法,然后循环一个字节一个字节比过去,时间复杂度是O(N)。

马上这位老哥就抛出如何做才好。

What’s Better

简单翻译下:我在网上乱翻了一番,想看看其他人是如何实现 CRC-64 的。但是大部分是拷贝 Redis 的(哎,恨其不争)。

然后 mattsta 发现了 stackoverflow 有个叫 Mark 的哥们自己写了个高速版 CRC-64 实现,他将 Mark 的实现和 Redis 的实现进行了对比,发现 Mark 的版本比 Redis 版本快了400% ,分别是1.6 GB/s 和400 MB/s。

但是! Mark 和 Redis 实现的 CRC-64 属于不同的版本,没错 CRC-64 算法有很多变体,于是 mattsta 把枪口暂时对准了 CRC。

原谅我的孤陋寡闻,我一直以为 pretty 和 girl 比较配,原来还能用来和 awful搭,我学到了,嗯。pretty f...。

然后 mattsta 说我们不能直接用 Mark 的实现,但是我们可以看看是Mark 的实现。

What’s Improved

mattsta 先展示了下 Redis 的实现,就是每个字节循环操作,然后查表。

然后他又贴出了快版本的实现。

可以看到也是查表法,不过一次不是处理 1 个字节而是 8 个字节。

mattsta 用了一个我觉得很搞笑的形容 tiger prepping to devour your entrails 。这新版本的代码看起来像一只老虎准备吃掉你的内脏!再坚持一会儿!这还是个超链接,我点过去真的是一只老虎图!

哈哈哈,容我先笑一会儿。这老哥很有意思!

然后他说强调了下算法的快的原因,就是利用多维数组查表,每次循环可以处理8个字节。

因此对于 500 MB 的输入来说 Redis 的版本需要 5 亿次循环,而新版本只要 6250 万次。

这个slicing-by-8是英特尔公司的研究人员于 2006 年发布,也就是说利用 8 个查找表,每个查找表包含另一个 256 字节的查找表来实现一次能处理 8 个字节的 CRC-64 算法。简单的说还是空间换时间的操作。

于是这位老哥找到了灵感,他要自己实现一个和 Redis 匹配的 CRC 算法。

Result

简单翻译下:就是经过一年的努力,mattsta 终于做出符合 Redis 匹配的 CRC-64 算法快速版本,而且作为额外奖励,也能用在CRC-16上噢,并且可以摒弃老版本源代码中一堆静态查找表。可以在需要的时候再动态生成,而不是总是拖着它们使代码膨胀。

我们来看看老版本的代码确实有一堆,我截了一小段。

也就是 mattsta 写的快速 CRC 实现版本-crcspeed,不仅速度更快,而且清减了代码。

然后 mattsta 来了一波不要相信我说的话,咱们来让数据说话(傲娇.jpg)。

通过 mattsta 自己的笔记本测出的 crcspeed 从耗时、吞吐量和每字节所需CPU周期三方面来看都优于 Redis 的实现。

Real-World Impact

mattsta 又指出 crcspeed 能给Redis 带来啥呢?

简单的翻译下就是:Redis 在生成 RDB 的时候会 fork 出子进程,因此采用的是写时复制,所以内存的增长取决于写入的负载,那么快速的结束 RDB 退出 fork 的子进程,用在 COW 的内存就会更少,而生成 RDB 的时候又用到了CRC-64 作为校验,那么 CRC-64 校验越快,RDB 生成的就越快,用于 COW 复制而使用的内存就越少。

并且

CRC-16来映射槽的时候,如果用户正在做一些古怪的事情,比如使用300 MB 的按键,那么快速的 CRC-16可以减少400% 的集群槽分配开销!

这个 If users are doing wacky things,我很是赞同,不管公司内部还是公司外部,你所实现的接口都要怀揣着使用者会以最大的恶意来调用去看待。

mattsta 说这是一个有效和高效的多方面的双赢!

我本以为文章都这里就差不多了,然而并没有。

Minor Notes

可以看出 mattsta 是不想造轮子的,但是实在是没有轮子啊!于是他只能自己实现一个,这是个新轮子!

Resources Consulted

然后他列出了他所参考的一些资源,他首先感谢了「A PAINLESS GUIDE TO CRC ERROR DETECTION ALGORITHMS」这篇文章。

让我们学一下感谢参考资料的正确姿势。

看到没,这才是感谢的正确姿势!我们来看下它所感谢的文章的样子。

有一说一,确实,纯路人。身为一个txt,编写良好、格式良好,有趣。在风格、布局和语气的所有方面都经过了专家的深思熟虑。

夸一番 mattsta 觉得还不够,还得加一点自己的想法。

简单的翻一下:在某种程度上,互联网已经失去了保存写的好的、格式良好的、信息丰富的指南以及常见问题的解答和平易近人的研究论文的能力。我们应该努力把那部分世界夺回来。这种损失该归咎于什么呢?对风格的过分依赖?CSS?还是 JavaScript?PHP?。

世界上最好的语言警告!

看到没,这才叫感谢。mattsta 追求纯干货,别给我整一些花里胡哨的!

而且这篇文章还让 mattsta 确信他没有能力实现一个 CRC-64 算法,因此实际上他是依靠 pycrc 来实现的。

然后这位老哥又说 yahyahyah,linux kernel 也是这样用的。

老哥还找到一篇介绍 slicing by 8 的放在英特尔网上的论文,不过现在已经没了。所以先嘲讽了一波英特尔,还顺带还吐槽了傻*谷歌代码,每次打开这个 pdf 都自动下载,而不是在线浏览。

这老哥真的对我胃口哈哈哈。mattsta 的文章还没结束,下面写的是一些关于实现的细节。有兴趣的朋友到时候可以看看,文末会放链接。我就不再跟下去了。

我们再来看身为 Redis Contributor 为何 mattsta 会写这一篇文章?不应该提 pr 直接解决吗?难道这算法有什么致命之处使得 Antirez 不接受?我们来追踪一下!

追踪事情的来龙去脉

首先这篇文章写于2014-12-22

mattsta 在 2013-12 开始了对 redis.io 的第一次提交。

随后 mattsta 就开始了对 Redis 的输出,在2014-04-01,提出了相关的 issue,并且附上了自己的对比。

提出的 issue 没有受到团队成员的响应,寂寞如雪,只有一位金毛小哥,为其打 call。这个issue 此时还是 open 的。

然后在当年,2014-11-23,mattsta创建了 crcspeed 库,并且提交了实现。

并在2014-12-22提交了pr,竟然和写文章是同一天!而且是先写了文章再提的 pr。一开始我以为是提了pr迟迟得不到采纳,然后才一怒之下写的文章。

可以看到隔了一天有团队人员回应了,他说我不知道这是否会被合并(我认为它应该) ,但是,该死的!这是一个伟大的提升!牛皮克拉斯!

2014-12-23,mattsta 又对 pr 做了一些补充说明。然而没有回应。

直到2015-01-10,mattsta 对 pr 又做了一波更新。

终于在2015-02-25号,等到了 antirez 的回复。

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 提升了多少。

其实双方我都能理解,所处角色不同。最终我们终于得知整个事情的来龙去脉,再附上一直 mattsta 的靓照,看来发量不错。

这篇文章讲述的就是这么个事儿。其实我就是带着八卦之心来看为何身为 Contributor 的 mattsta 提的明显正确的 pr 没有被 merge,至于什么 CRC 的我不关心哈哈哈哈。

当然 mattsta 的钻研之心值得我们学习,当然还有他那搞笑的形容和五彩斑斓的感谢。而 antirez 对 pr 的严谨也值得我们效仿。

链接

https://matt.sh/redis-crcspeed

https://github.com/mattsta?tab=repositories


我是 yes,从一点点到亿点点,我们下篇见

往期推荐:

面试官:知道时间轮算法吗?在Netty和Kafka中如何应用的?

消息队列面试热点一锅端

Kafka和RocketMQ底层存储之那些你不知道的事

图解+代码|常见限流算法以及限流在单机分布式场景下的思考

表弟面试被虐我教他缓存连招

面试官:说说Kafka处理请求的全流程

Redis 作者 Antirez 与 Contributor Mattsta 之间关于 CRC 的 Battle的更多相关文章

  1. Redis作者谈Redis应用场景(转)

    毫无疑问,Redis开创了一种新的数据存储思路,使用Redis,我们不用在面对功能单调的数据库时,把精力放在如何把大象放进冰箱这样的问题上,而是利用Redis灵活多变的数据结构和数据操作,为不同的大象 ...

  2. Redis作者谈Redis应用场景

    Redis作者谈Redis应用场景 毫无疑问,Redis开创了一种新的数据存储思路,使用Redis,我们不用在面对功能单调的数据库时,把精力放在如何把大象放进冰箱这样的问题上,而是利用Redis灵活多 ...

  3. 转:Redis作者谈Redis应用场景

    毫无疑问,Redis开创了一种新的数据存储思路,使用Redis,我们不用在面对功能单调的数据库时,把精力放在如何把大象放进冰箱这样的问题上,而是利用Redis灵活多变的数据结构和数据操作,为不同的大象 ...

  4. redis学习研究--Redis作者谈Redis应用场景

    毫无疑问,Redis开创了一种新的数据存储思路,使用Redis,我们不用在面对功能单调的数据库时,把精力放在如何把大象放进冰箱这样的问题上,而是利用Redis灵活多变的数据结构和数据操作,为不同的大象 ...

  5. Redis作者谈Redis应用场景(转)

    add by zhj : 这是Redis的作者antirez在他的技术博客中写的一篇文章 英文原文:take-advantage-of-redis-adding-it-to-your-stack 译文 ...

  6. [转]Redis作者:深度剖析Redis持久化

    From : http://www.iteye.com/news/24675 Redis是一种面向“key-value”类型数据的分布式NoSQL数据库系统,具有高性能.持久存储.适应高并发应用场景等 ...

  7. 来自Redis 作者的看法 —— Twemproxy

    虽然大量用户使用Redis节点的大型农场,但从项目本身来看,Redis主要是单实例业务. 我有很大的计划与项目一起分发,在某种程度上我不再评估Redis的任何线程版本:对我来说,从Redis的角度看, ...

  8. Redis作者:深度剖析Redis持久化

    Redis是一种面向“key-value”类型数据的分布式NoSQL数据库系统,具有高性能.持久存储.适应高并发应用场景等优势.它虽然起步较晚,但发展却十分迅速. 近日,Redis的作者在博客中写到, ...

  9. 网站缓存技术总结( ehcache、memcache、redis对比)

    网站技术高速发展的今天,缓存技术已经成为大型网站的一个关键技术,缓存设计好坏直接关系的一个网站访问的速度,以及购置服务器的数量,甚至影响到用户的体验. 网站缓存按照存放的地点不同,可以分为客户端缓存. ...

随机推荐

  1. OI常用数学定理&方法总结

    组合数计算($O(n)$) https://www.cnblogs.com/linzhuohang/p/11548813.html Lucas定理 如果要计算很大的组合数,但模数较小,考虑这个方法 对 ...

  2. 基于id3算法根据房价数据进行画图预测python

    根据已给的波士顿房价数据,对波斯顿房价进行预测.即,实现给出若干条件(如房间数.社区的低收入阶层的比率和镇上学生与教师数量比例的部分数据),要能说出给出的条件是否能够有效进行预测,如可以做有效预测,则 ...

  3. 硬核干货:4W字从源码上分析JUC线程池ThreadPoolExecutor的实现原理

    前提 很早之前就打算看一次JUC线程池ThreadPoolExecutor的源码实现,由于近段时间比较忙,一直没有时间整理出源码分析的文章.之前在分析扩展线程池实现可回调的Future时候曾经提到并发 ...

  4. java 区块

    方法区:存放staic变量,方法签名,类信息,字段等 堆:存放对象数据,string常量 栈:存放对象的引用,操作数,没逃逸但是逃逸分析且被编译器产生逃逸优化的对象数据

  5. 剑指 Offer 19. 正则表达式匹配

    题目描述 请实现一个函数用来匹配包含'. '和'*'的正则表达式.模式中的字符'.'表示任意一个字符,而'*'表示它前面的字符可以出现任意次(含0次).在本题中,匹配是指字符串的所有字符匹配整个模式. ...

  6. Mybatis源码学习第六天(核心流程分析)之Executor分析(补充)

    补充上一章没有讲解的三个Executor执行器; 还是贴一下之前的代码吧;我发现其实有些分析注释还是写在代码里面比较好,方便大家理解,之前是我的疏忽,不好意思 @Override public < ...

  7. 我的T440p出现怪事情了

    装上系统后,再稍微装些软件,或是打补丁,升级驱动什么的,再重启就起不来了. 也就是说,装一次系统只能好一次,关机或是重启就启动不了了,现象是在黑屏界面转两下就转不动了. 鼓捣一个周末也无效,昨天上系统 ...

  8. centOS7 设置mysql数据库外网可以访问

    1.查看目前防火墙是否对外开发了3306端口 firewall-cmd --list-all 2.查看3306端口是否开放 firewall-cmd --query-port=3306/tcp no ...

  9. PostgressQL

    环境 ubuntu18.04 docker 中 pull postgres:10 images 连接 进入容器内部 docker exec -it [mypostgres] /bin/bash 查看p ...

  10. SpringMVC-Controller&RestFul

    Controller与RestFul 目录 Controller与RestFul 1. Controller 1. 控制器Controller 2. 利用接口定义控制器 1. 实现Controller ...