選單

如果產品中需要壓縮功能,我們應該如何選擇壓縮演算法?

作者 | 段寬軍

審校 | 蔡芳芳

看過很多壓縮相關的技術文章,大家都在講各種壓縮演算法的技術實現原理及各壓縮演算法之間的壓縮率的對比,哪個壓縮演算法好等等。這些技術文章非常好,可以指引我們在技術上不斷鑽研。本文將從另外一個大家講的還比較少的角度,和大家一起探討下如何在產品中使用好壓縮演算法。

一、認識壓縮演算法

1 壓縮演算法的歷史

壓縮演算法的歷史,如同壓縮演算法一樣,閃耀著神奇奧妙的光芒。

最早使用壓縮概念的是 19 世紀中期的摩爾斯碼,在當時還是使用電報進行資訊傳輸的,電報傳輸訊號速度非常慢,摩爾斯意外發現大部分英文單詞中使用某些字母的頻率特別高,所以就想到用短的脈衝訊號去代替出現頻率高的字母,這樣就能在更短的時間內傳送出去更多的資訊,這個想法也就是最早出現的壓縮演算法的概念了。

過了 100 多年後,夏農(Claude Elwood Shannon)發表了著名的“

通訊的數學原理

”論文,最早提出了資訊熵的概念,建立了資訊度量與壓縮的理論基礎。

夏農是資訊理論的創始人,數學家,同時也是愛迪生的遠房親戚(這一家族肯定是祖墳上冒青煙了)。

為什麼壓縮和他有關呢,因為資訊量的度量公式就是由他推匯出來的。大家知道物體的重量是可以透過稱重準確得到的,對於看不見摸不到的資訊量如何度量呢?夏農就是帶著這個問題在不斷探索和鑽研,在參考了當時把熱量成功度量出來的熱力學第二定律的理論基礎上,建立了資訊熵計算公式,成功的讓看不見的資訊量也能夠像物體的重量一樣可以準確的稱出來了。

同時他也是我們現在最常見的位元位的建立者,使用 0 和 1 兩種狀態來表示最短的資訊量(如同世界上的有和無一樣),也就是資訊的最小單位。咱們現在使用的 byte,也是在其基礎上的一個擴充套件應用。同時他的公式也是第一次用數學語言闡明瞭機率與資訊冗餘度的關係,簡單來說,就是你一句話的意思用不同方式重複說了三遍,實際還是一句話的資訊量,重複不帶來資訊量。

幾年之後,哈夫曼(David A。 Huffman)在 1952 年提出了 Huffman 編碼,這個發明在壓縮演算法界裡算是里程碑式的,它讓前面的理論可以實實在在的落地。Huffman 編碼並不是新理論,而是對早在 100 多年前就提出的摩爾斯碼理論的一個非常巧妙的應用。

Huffman 編碼仍然使用最初由摩爾斯提出的核心壓縮理論,即頻率高的用短碼錶示,頻率低的用長碼錶示。但短碼如何編碼、長碼如何編碼及如何最小化資訊量傳輸,這些問題在之前一直困擾著人們,而哈夫曼設計的 Huffman 樹,讓這些問題都得到了完美解決。

後來像 UNIX 上的 compact、DOS 中的 SQ,都是基於 Huffman 編碼實現的,再後來出現的 WinRAR 和 GZIP 中也能看到 Huffman 編碼的身影。

同時,在那幾年還出現了費諾編碼。費諾編碼是在夏農編碼的基礎上改進的,目的是減小編碼長度。它按機率對訊號源進行排序,然後對訊號源進行切分,預期是切分後的兩部分內資訊源機率之和最接近為最佳。用這種方式編碼後的碼長會小於夏農編碼。它是一種機率匹配編碼,並不是最佳編碼方式。

字典編碼大約是在 1997 年出現的,Abraham Lempel、Jacob Ziv 發表了他們開創性的 LZ77 演算法,這是第一個使用字典資料的演算法。LZ77 使用了一種叫滑動視窗的動態字典。1978 年,同樣是這兩個人又釋出了他們的 LZ78 演算法,該演算法也使用了字典,與 LZ77 不同的是,該演算法會解析輸入資料並生成靜態字典,而不是隨時生成的動態字典。

多媒體出現後,壓縮演算法又出現了有失真壓縮技術,有失真壓縮是透過犧牲影象質量獲得流暢的體驗,解決了多媒體檔案普遍過大導致播放時卡、慢等現象。

2 常用的壓縮軟體

壓縮軟體大家並不陌生,Windows 下常用的壓縮軟體有 WinZip、WinRAR、cab 和 7z 這些, Linux 下常用的有 tar、zip、gzip 和 bzip2 等軟體。這些軟體為了提升壓縮率,會使用多種壓縮演算法來實現。即便是相同的壓縮演算法,在不同軟體中的實現可能也會有較大差別,進而有優劣之分,這也就是為什麼使用相同壓縮演算法的壓縮軟體之間壓縮率及效能差別很大的原因了。

3 壓縮演算法的分類

如果產品中需要壓縮功能,我們應該如何選擇壓縮演算法?

二、壓縮演算法特點與本質

壓縮演算法的特點如下:

1)新壓縮演算法總是在不斷湧現

2)壓縮率與壓縮速度總成反比

3)不同資料來源對壓縮率影響很大

壓縮的本質是對資訊進行再編碼,即相同資訊使用另一種更簡潔的方式重新表達。

人們在生活中到處可以看到一些壓縮方法,同時也在不知不覺中使用著,如簡稱就是一種典型的壓縮方法。“中華人民共和國”我們就簡稱為“中國”,“中國交通管理局”我們也習慣用“交管局”來表示,使用簡稱讓我們提高了效率。這些壓縮方法通常也需要帶著一個固定的詞典,在詞典中把“中國”再翻譯回原來的“中華人民共和國”,簡稱的詞典都裝在我們每個人的腦子裡,所以可以相互交流。

三、壓縮演算法的使用

1 常見的使用不妥的地方

我觀察到的大部分壓縮演算法使用不妥的地方是對自己的資料不瞭解,同時對所使用的壓縮演算法的原理也沒有理解,只是看到評測文章說哪個壓縮演算法好,就花不少精力移植到自己的專案中,結果一跑和預期相差甚遠。

為什麼會出現這種現象呢?主要是你的資料和評測的資料不一樣。拋開壓縮物件說壓縮演算法如何牛,就是在耍流氓。有人可能要說了,我這壓縮演算法不管你是什麼資料,我都能壓縮得很好。那好,我給你的資料就是你壓縮後的資料,看你還能不能再壓下去。

所以,壓縮演算法一定是要和壓縮物件緊密關聯的,只有瞭解清楚自己的資料,才能壓出好效果。

2 正確的使用方法(一核二平原則)

我認為正確地使用壓縮演算法,要抓好一個核心、做好兩個平衡。

一個核心,就是緊緊抓住資料的特點,根據特點選擇合適的壓縮演算法。兩個平衡,一個是要做好壓縮速度與壓縮率的平衡,二是做好投入與收益的平衡。

下面我分別展開來詳細描述下:

一個核心

如何抓好一個核心,關鍵是洞察及發現自己資料的特點,並有效利用好這些特點。這裡我以 TDengine 中的壓縮演算法為例。

TDengine 是處理時序資料的資料庫,時序資料的兩個特點:

採集的資料都是時序的

採集的資料大多是在一定範圍內波動的

根據以上這兩個特點,我們在資料儲存的時候採用了列式儲存。列式儲存可以讓相同型別的資料儘可能地放在一起,這就為下一步的高效壓縮創造了條件。

按列壓縮的時候,我們會根據不同列的資料型別採用不同的壓縮演算法:

時間序列型別

:我們利用了時間序列資料的穩定增長且有固定差值的兩個特點,選擇使用 delta-of-delta 壓縮演算法,此壓縮演算法記錄的是差值的差值,如果我們採集的頻次是固定的且為 1 秒一次,用此演算法編碼後需要記錄的值將全部是零,這樣就可以極大減小要儲存的實際資訊量了。

整數型別:

裝置採集的整數型別一般都是自然界產生的常量資料,如溫度、溼度、電壓、電量等。這些數的共同特點是絕對值都相對較小,並在一定範圍內波動。針對這些特點,我們對整數型別的資料採用了小整數編碼(ZIG-ZAG)技術配合前導零記錄(simple8B)的壓縮技術,實現資訊量的壓縮。ZIG-ZAG 把原來前置的符號位後移,把有值部分儘可能湊一起,讓更多的 0 可以成片連線起來,方便壓縮。

字串資料型別

:時序資料庫中儲存的字串型別的資料,一般都是裝置編號或者裝置名稱等,這些名稱大部分習慣使用英文字母 + 數字來表示,我們使用通用的字串壓縮演算法,實現了這部分資料的壓縮。

還有其它各種型別的資料,如 float、bool 型別等,我們都採用了適合其資料特點的壓縮演算法,得到了較好的壓縮效果。

TDengine 中的壓縮演算法,緊緊抓住不同類別資料的特點,使用最適合這類資料的壓縮演算法,達到了 TDengine 的超高壓縮率和超高壓縮效能,是一個核心理唸的典型應用。如果大家對具體實現感興趣,也可以參考 TDengine 的原始碼(https://github。com/taosdata/TDengine)。

兩個平衡

壓縮速度與壓縮率的平衡

壓縮率越高越好,這個標準只存在於技術領域純談技術之時。

在實際生產生活中,必須要和它的另一個矛盾面——壓縮速度——結合起來看。在不斷提高壓縮率的同時,壓縮速度必然要跟著下降,使用者需要根據自己的實際應用情況,在兩者間把握和拿捏出一個好的平衡點來,達到既不會太影響業務處理速度,也可以收穫一個好的壓縮率的效果,從而節約儲存空間。

TDengine 在處理這個問題時,是優先要保證採集資料的流量峰到來時,壓縮資料這一環節不會存在資料積壓現象,並且還要留有 10% 的處理空間。要保證採集資料實時入庫的核心業務通暢是優先順序最高的,其次才是儘可能把資料壓縮下來,為使用者節約磁碟空間。

也就是說,TDengine 拿捏這個平衡度,是按主要矛盾、次要矛盾的權衡觀點來處理的。對於不同的業務,這個平衡點是不一樣的,需要自己把握好。

投入與收益的平衡

另一個要把握好的平衡就是投入與收益。處理大型業務資料的壓縮,通常我們會選擇多種壓縮演算法和壓縮策略來實現。對業務資料切分的越精細,選擇的壓縮演算法及策略就越準確,壓縮率和壓縮效能也會越高。

最佳化壓縮演算法和壓縮策略,是一條永無止境的道路:大模組層面最佳化不下去,可以最佳化小模組;小模組完了,還可以最佳化小模組裡的每個函式;函式完了還有彙編層面的最佳化。

那我們要不要把壓縮演算法一直最佳化下去呢?

我覺得這個需要在投入與收益之間找到一個平衡點。壓縮演算法的最佳化有這樣的規律,前期投入 10 分的人力,能優化出 20 分的成績,後期投入 10 分的人力,有可能只能優化出 5 分的成績。

我們需要評估在產品或專案中壓縮所佔據的位置、壓縮對上下游環節的影響程度,做出一個合理的最佳化截止點出來。比如說有些產品或專案中的壓縮功能,並不是很重要,只是一個輔助功能,比如說一些定期備份壓縮儲存的功能,用到的頻率也不高,只有出問題時才會讀取出來用一下,像這樣的應用中沒必要投入太大精力把壓縮最佳化到極致,這個功能並非產品關鍵功能,快速地構建一個功能穩定的壓縮方法應該就可以了。

TDengine 中的壓縮環節對整個產品而言還是比較重要的:一是使用頻繁,讀寫查詢都在不斷呼叫;二是壓縮率的大小決定了能給使用者節約多少硬碟,直接關係著使用者的儲存成本;三是壓縮演算法效能決定著使用者資料寫入及查詢的速度,都是產品核心功能。

所以我們在這方面投入的資源是比較大的,不斷最佳化壓縮演算法,不斷提升壓縮率和壓縮效能。但我們也不會無限制地投入大量人力在這塊兒,而是會根據公司的當前規模及人員情況,做到一個合理的截止點即可,後續仍然保留著需要投入更多資源去最佳化的空間。

四、總結

前面介紹了壓縮演算法的歷史、分類及特點後,又重點介紹了壓縮演算法的使用,丟擲了自己的觀點,旨在引導大家在產品中正確地使用壓縮演算法。

如果實在記不住太多東西,讀完這篇文章,我希望你能記住四個字——“一核二平”,這樣就應該有所收穫了。使用壓縮演算法時要抓住資料特點這一個核心,然後平衡好壓縮率與壓縮速度,平衡好投入及收益就好了。

作者簡介:

段寬軍,濤思資料高階架構師。主要負責流計算、資料儲存、壓縮演算法等相關的工作。