你好,我是 yes。java
這篇文章是關於 Redis 的一個技術類小八卦,我看完直呼 666。linux
開始表演
我無心間查到一位叫 mattsta 的老哥的 14 年寫的文章,這老哥說 Redis 如今實現的 CRC 算法太簡陋了!他有能提高 4 倍性能的加強版 CRC 算法 - CRCSpeed!git
我立刻去看了我本地 Redis 5.0 版本的源碼,發現 CRC 算法並無採納他的加強版,仍是老的實現。github
我又去看了最新 6.0 的版本,發現 CRC-64 改爲了 CRCSpeed 的實現,在 2020 年 4 月 28 號提交的,提交者是 antirez,也就是 Redis 的做者。面試
這就讓我愈加的好奇了,這 2014 到 2020 跨度有點大啊,到底發生了啥?redis
而後我又去翻了 mattsta 的 Github ,追蹤了整個事情發展的歷史脈絡,事情愈來愈有意思了。算法
我已經火燒眉毛地想和你們一塊兒再來看一遍這哥們的文章,過一遍這件事。數組
這哥們的文章是英文的,不太重點我在圖中都標紅了,也都作了相應翻譯。微信
Fancy CRCing You Here
這老哥文章標題就有那味兒!Fancy CRCing You Here
,這是要開炮了。memcached
而後這老哥文章的第一句話就很爲人着想。
簡單翻譯下就是 short 的人直接點連接看結果,那我確定不 short 的啊,因此我選擇接下去看。
緊接着這老哥就拋出瞭如下的話:
Redis 的 CRC-64 的實現有不少人拷貝到他們的項目中使用,CRC-16 也有少許拷貝。這算法是能用啊,可是能夠有更好的實現。
當時我搜了下, Github 上有 2353 個項目用到了 Redis 的 CRC-64 實現,325 個項目用到了 Redis 的 CRC-16 實現。
這位老哥爲他們感到可惜,他說:唉,他們值得更好的CRC算法呀。
緊接着老哥開啓了第一波輸出。
簡單翻一下:
-
Redis 決定忽略吞吐量和延遲方面的提升,由於他們更喜歡像1999年那樣編碼。
-
代價就是他們過期的傳統設計慢了40倍。你們幹得漂亮啊。(不知道這老哥是筆誤仍是故意把4倍在開頭寫成40倍的,我就揣測下:),那個outdated legacy design超連接跳過去就是標註着 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,沒錯他噴了一波 CRC 算法自己。
原諒個人孤陋寡聞,我一直覺得 pretty 和 girl 比較配,原來還能用來和 awful搭,我學到了,嗯。pretty fu..。
而後 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 也是這樣用的,yahyahyah。
老哥還找到一篇介紹 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 作了一些補充說明,然而 antirez 仍是沒有迴應。
直到 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 提高了多少,即便我知道這玩意確定是 ok 的。
其實雙方我都能理解,所處角色不一樣,我只能說若是我是 mattsta 我可能會在內心罵 antirez,若是我是 antirez 我以爲就該這樣。
來張 mattsta 老哥的靚照,哈哈頭髮不少!
這篇文章講述的就是這麼個事兒。
我就是帶着八卦之心來看爲什麼身爲 Contributor 的 mattsta 提的明顯正確的 pr 沒有被 merge,至於什麼 CRC 的我不關心哈哈哈哈。
mattsta 老哥的鑽研之心值得咱們學習,固然還有他那搞笑的形容和五彩斑斕的感謝。
歡迎關注個人公衆號【yes的練級攻略】,更多硬核文章等你來讀。
參考連接
-
https://matt.sh/redis-crcspeed
-
https://github.com/mattsta?tab=repositories
我是 yes,從一點點到億點點,咱們下篇見。
往期推薦:
本文分享自微信公衆號 - yes的練級攻略(yes_java)。
若有侵權,請聯繫 support@oschina.cn 刪除。
本文參與「OSC源創計劃」,歡迎正在閱讀的你也加入,一塊兒分享。