信息論III:尋找序列化的極限

本系列全部章節一覽:


  1. JSON的“噪音”與“信噪比”

  2. 噪音量的理論上限、逆波蘭表達式

  3. 信息論與壓縮技術:字符串vs字節串

  4. 最優二叉樹、FPS/2.0

  5. Huffman編碼

  6. Message Pack

  7. Message Pack 的 Huffman 樹

  8. 前綴 VS 分隔符

  9. Message Pack 缺陷、宿主環境Bug

  10. 序列化的極限、兩個基本公理

  11. UTF-8極限壓縮

  12. 有理數:變長類型偏移術

  13. 字典壓縮大法

  14. 尾部殘缺問題

  15. Ultra Pack與時空置換原理

  16. V8引擎玄學(What's happening under the hood)


來自【奇怪的知識】系列的第三篇,承接上文《最優二叉樹與Huffman編碼》的第1~第5章,本文從第6章開始。

啦啦啦。


06


Message Pack

Message Pack,以下簡稱msp或msgPack,就是這樣一個流行於民間,基於Huffman編碼,兼容json的二進制序列化格式。

msp兼容json是因爲msp支持json的所有數據類型(4個基本類型及2個複合類型),除此之外msp還有自己的類型,包括純粹二進制格式(也叫字節串)、datetime格式、自定義保留類型、變長基本類型。

msp之所以基於Huffman指的是,msp中每一種數據類型就是一個編碼對象。

變長基本類型包括變長實數、變長字符串、變長字節串。變長類型意味着你可以用儘可能短的空間存放更“小”的數據,比如127以內的正整數只佔1字節。

msp還支持“無縫流化”。想象一下json想要流化異常麻煩,有多種“流接”的解決方案,最常用的ndjson也要消耗換行字符來分割每個json。但是msp因爲通過前綴來限定長度,無需分隔符/終止符,前後2個msp對象可以無縫銜接。

舉個例子。

圖中這個demo裏面,29字節的json對象經過msp壓縮之後變成20字節。圖中高亮的字節/字符代表有效信息量,剩下灰色部分代表噪音,信息量 / 噪音 = 信噪比。顯然msp的信噪比更高,體積更小。當然了,這樣計算信噪比是不嚴謹的,實際情況還要考慮類型使用概率等因素,但舉個例子足夠了。

關於msp和json的全面對比,可以參考《MessagePack:最可能取代JSON的存在》這篇文章,文章的結論是:msp理論上比json更小更快更豐富

07


Message Pack 的 Huffman 樹

誇完了msp,來扒一扒msp的specification。

把msp支持的所有數據類型按照前綴的編碼放到一棵樹上就得到上圖的Huffman樹,由於樹太大,我將“110前綴節點”爲分界點,將msp的Huffman樹分爲“110之前”和“110之後”兩部分:110之前都是長度爲1~4bit的常用類型,110之後都是長度爲8bit的相對“冷門”一點的類型。

08


前綴 VS 分隔符

圖中的測試數據是在python平臺下進行的,爲什麼選擇python平臺而不是JS平臺的原因文章結尾會說明ε=ε=ε=┏(゜ロ゜;)┛。

可以看到,python3下具有相同信息量的json和msp,msp的體積減少16.2%,解碼速度大幅提升,只有編碼消耗的時間更長,總的來說msp性能優於json。

可是爲什麼msp編碼耗時更長呢?我個人的猜測是,json屬於利用分隔符劃分元素的格式,msp屬於通過前綴劃分元素的格式。前綴的好處在於可以加速解碼速度,因爲前綴暗示了下一個元素的長度,讓解碼器可以“跳着”解碼,不像json那樣需要逐字符掃描,遇到分隔符或者休止符才停止。

但編碼和解碼是一對逆過程,解碼的速度提升了,編碼的速度自然就要下降,這是不可違背的自然規律。對於分隔符型序列化格式,編碼的過程就是一條龍式的平鋪過程,沒有任何停頓,但前綴型序列化時需要在每個元素寫入完成後計算元素的長度,然後將長度插入到元素開頭,自然要更多的時間。

這也就是msp在編碼的速度上慢於json的原因。

09


MsgPack的缺陷

雖然不知道msp的“信噪比”,但肉眼是能看得出msp也是有一些缺陷的。比如msp的Huffman樹有待優化,還記得之前“110之後”的那棵樹嘛,那棵樹上32種數據類型的前綴長度完全對稱,常識告訴我們,越整齊的東西性能越低,Huffman樹越“整齊”越說明了變長編碼沒有得到好的設計,畢竟不可能每種類型的使用頻率都一樣。

msp的保留類型太多,它居然有9種保留類型,包括擴展類型和“never used”類型。保留類型太多也沒什麼意義,畢竟保留類型使用的頻率也很低。

msp的生態不夠完善,雖然有幾十種語言開源編解碼器,但沒有標準庫支持msp很難得到官方認可。

言而總之,msp可進一步壓縮,壓縮的極限在哪裏?誰也不知道。

10


序列化的極限

從一開始的文本格式到後來的序列化格式,我們一直在尋找序列化的極限,這個極限究竟在何方,不能盲目的尋找,似乎要給這個極限下一個定義。於是我指定了2個原則,作爲序列化極限的基本公理,請大家評鑑一下,看看合不合理:

  1. 原則一:任意的字節串都有意義

  2. 原則二:不同的字節串都有不同的意義

這兩句話啥意思?對於原則一,假如給你一副只有0和1的鍵盤,讓你隨便敲,將你一頓輸出後的字節串送給一個解碼器去解碼,如果解碼總是成功則說明這個編碼格式遵守原則一,如果可能報錯則違背原則一。

很顯然無論是json,msp,甚至是utf-8都違背原則一,而ASCII遵守原則一,因爲一個字節表示的256種字符都存在。實際上絕大多數變長編碼格式都違背原則一。

對於原則二,它表示n種字節串有n種含義,如果2個不同的字節串表達了相同的含義,從信息論的角度,這是一種浪費。

這兩個原則都是保證了數據體積壓縮到極限,並沒有考慮編解碼的速度,由於本文的主題只關心空間,不考慮時間,所以時間複雜度問題不在本系列研究。

言而總之,只要一個序列化格式(編碼格式)滿足了原則一和原則二,我們就稱它達到了序列化的(空間)極限。

奇怪的知識越來越多了

11


UTF-8極限壓縮

爲了達到序列化的壓縮極限,我們給每種數據類型挨個分析,先從最簡單的字符串開始。

uft8是耳熟能詳的字符編碼了,而且是變長編碼,utf8的Huffman表如上圖,目前utf8字符的長度從1~4字節不等,每種字符又有不同的前綴,但存在2種特殊的前綴,分別是:

  1. 後續字節前綴(10)

  2. 保留類型前綴(11111)

後續字節前綴10是除首字節以外的字節前綴,單字節的字符沒有。雖然10前綴的存在具有字符校驗、逆向索引的好處,但從信息論的視角,這是多餘的噪音罷了,完全沒有存在的必要,具體原因參照這篇文章:《這難道是UTF-8字符編碼的設計缺陷?》

保留類型前綴11111是爲了預留給未來可能出現的新字符做準備,它們主要是長度超過4字節的字符們。

無論是10還是11111都違反了原則一,因爲在不恰當的位置出現這些前綴直接導致utf8解析失敗。

這兩個前綴之所以特殊是因爲它們在utf8的Huffman樹上存在但不能表示具體的編碼對象,如下圖:

圖中標紅的2個前綴就是違反原則一的2個前綴,如果把這兩片葉子從樹上摘掉會怎麼樣呢?摘掉以後就成爲了minUTF8,也就是圖中第二幅更簡單的Huffman樹,minUTF8是UTF8的壓縮版本,去掉了無用的前綴,大大減少了存儲成本。

minUTF-8這個名字是我隨便取的,僅僅代表一種可能的編碼方案,並不一定能在實際中產生應用。

<未完待續>

下期預告:《宿主、時空置換、V8玄學》


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章