Protobuf有沒有比JSON快5倍?用代碼來擊破pb性能神話
轉 http://www.sohu.com/a/136487507_505779
2017-04-26 07:58 程序設計 /58 /技術導讀:Google 的 Protocol Buffers 在數據編碼的效率上似乎被神化了,一直流傳性能是 JSON 等本文格式 5 倍以上,本文通過代碼測試來比較 JSON 與 PB 具體的性能差別到底是多少。作者陶文,轉載請註明來自高可用架構「ArchNotes」
陶文,技術極簡主義者。認為好的技術是應該是對開發者友好的。一直致力於用技術改進研發效率和開發者體驗。jsoniter [4] 作者,jsoniter 就來自於要不要用 Thrift 替代 JSON 的思考。我認為通過引入 IDL 和高效率的編解碼庫,可以讓 HTTP + JSON 這樣對開發者體驗有好處的技術長久地生存下去。
拿 JSON 襯托 Protobuf 的文章真的太多了,經常可以看到文章中寫道:“快來用 Protobuf 吧,JSON 太慢啦”。但是 Protobuf 真的有那麽牛麽?我想從 JSON 切換到 Protobuf 怎麽也得快一倍吧,要不然對不起付出的切換成本?
然而,DSL-JSON 居然聲稱在 Java 語言裏, JSON 可以和那些二進制的編解碼格式性能不相上下 [1]!
這太讓人驚訝了!雖然你可能會說,咱們能不用蘋果和梨來做比較了麽,兩個東西根本用途完全不一樣,用 Protobuf 是沖著跨語言無歧義的 IDL 的去的,才不僅僅是因為性能!這個我同意,但是仍然有那麽多人盲目相信 Protobuf 一定會快很多,因此我覺得還是有必要通過本文徹底終結一下這個關於速度的傳說。
DSL-JSON 的博客裏只給了他們的測試結論,但是沒有給出任何原因,以及優化的細節。這很難讓人信服數據是真實的。你要說 JSON 比二進制格式更快,真的是很反直覺的事情。稍微琢磨一下這個問題,就可以列出好幾個 Protobuf 應該更快的理由:
-
更容容易綁定值到對象的字段上。JSON 的字段是用字符串指定的,相比之下字符串比對應該比基於數字的字段 tag 更耗時。
-
JSON 是文本的格式,整數和浮點數應該更占空間而且更費時。
-
Protobuf 在正文前有一個大小或者長度的標記,而 JSON 必須全文掃描無法跳過不需要的字段。
但是僅憑這幾點是不是就可以蓋棺定論了呢?未必,也有相反的觀點:
-
如果字段大部分是字符串,占到決定性因素的因素可能是字符串拷貝的速度,而不是解析的速度。在這個評測 [2] 中,我們看到不少庫的性能是非常接近的。這是因為測試數據中大部分是由字符串構成的。
-
影響解析速度的決定性因素是分支的數量。因為分支的存在,解析仍然是一個本質上串行的過程。雖然 Protobuf 裏沒有 [] 或者 {},但是仍然有類似的分支代碼的存在。如果沒有這些分支的存在,解析不過就是一個 memcpy 的操作而已。只有 Parabix 這樣的技術才有革命性的意義,而 Protobuf 相比 JSON 只是改良而非革命。
-
也許 Protobuf 是一個理論上更快的格式,但是實現它的庫並不一定就更快。這取決於優化做得好不好,如果有不必要的內存分配或者重復讀取,實際的速度未必就快。
有多個 benchmark 都把 DSL-JSON 列到前三名裏,有時甚至比其他的二進制編碼更快。經過我仔細分析,原因出在了這些 benchmark 對於測試數據的構成選擇上。因為構造測試數據很麻煩,所以一般評測只會對相同的測試數據,去測不同的庫的實現。這樣就使得結果是嚴重傾向於某種類型輸入的。比如 [3]選擇的測試數據的結構是這樣的
點擊圖片可以放大瀏覽
無論怎麽去構造 small/medium/large 的輸入,benchmark 仍然是存在特定傾向性的。而且這種傾向性是不明確的。比如 medium 的輸入,到底說明了什麽?medium 對於不同的人來說,可能意味著完全不同的東西。所以,在這裏我想改變一下遊戲的規則。不去選擇一個所謂的最現實的配比,而是構造一些極端的情況。這樣,我們可以一目了然的知道,JSON 的強項和弱點都是什麽。通過把這些缺陷放大出來,我們也就可以對最壞的情況有一個清晰的預期。具體在你的場景下性能差距是怎樣的一個區間內,也可以大概預估出來。
好了,廢話不多說了。JMH 擼起來。benchmark 的對象有以下幾個:
-
Jackson:Java 程序裏用的最多的 JSON 解析器。benchmark 中開啟了 AfterBurner 的加速特性。
-
DSL-JSON:世界上最快的 Java JSON 實現
-
Jsoniter: 我抄襲 DSL-JSON 寫的實現。特別申明:我是 Jsoniter 的作者。這裏提到的所有關於Jsoniter 的評測數據都不應該被盲目相信。大部分的性能優化技巧是從 DSL-JSON 中直接抄來的。
-
Fastjson:在中國很流行的 JSON 解析器
-
Protobuf:在 RPC (遠程方法調用)裏非常流行的二進制編解碼格式
-
Thrift:另外一個很流行的 RPC 編解碼格式。這裏 benchmark 的是 TCompactProtocol
Decode Integer
先從一個簡單的場景入手。毫無疑問,Protobuf 非常擅長於處理整數
庫 | 相比 Jackson | ns/op |
Protobuf | 8.20 | 22124.431 |
Thrift | 6.6 | 27232.761 |
Jsoniter | 6.45 | 28131.009 |
DSL-JSON | 4.48 | 40472.032 |
Fastjson | 2.1 | 86555.965 |
Jackson | 1 | 181357.349 |
從結果上看,似乎優勢非常明顯。但是因為只有 1 個整數字段,所以可能整數解析的成本沒有占到大頭。所以,我們把測試調整對象調整為 10 個整數字段。再比比看
點擊圖片可以放大瀏覽
庫 | 相比 Jackson | ns/op |
Protobuf | 8.51 | 71067.990 |
Thrift | 2.98 | 202921.616 |
Jsoniter | 3.22 | 187654.012 |
DSL-JSON | 1.43 | 422839.151 |
Fastjson | 1.4 | 432494.654 |
Jackson | 1 | 604894.752 |
這下優勢就非常明顯了。毫無疑問,Protobuf 解析整數的速度是非常快的,能夠達到 Jackson 的 8 倍。
DSL-JSON 比 Jackson 快很多,它的優化代碼在這裏
點擊圖片可以放大瀏覽
整數是直接從輸入的字節裏計算出來的,公式是 value = (value << 3) + (value << 1) + ind; 相比讀出字符串,然後調用 Integer.valueOf ,這個實現只遍歷了一遍輸入,同時也避免了內存分配。
在這個基礎上做了循環展開
點擊圖片可以放大瀏覽
Encode Integer
編碼方面情況如何呢?和編碼一樣的測試數據,測試結果如下:
庫 | 相比 Jackson | ns/op |
Protobuf | 2.9 | 121027.285 |
Thrift | 0.17 | 2128221.323 |
Jsoniter | 2.02 | 173912.732 |
DSL-JSON | 2.18 | 161038.645 |
Fastjson | 0.81 | 431348.853 |
Jackson | 1 | 351430.048 |
不知道為啥,Thrift 的序列化特別慢。而且別的 benchmark 裏 Thrift 的序列化都是算慢的。我猜測應該是實現裏有不夠優化的地方吧,格式應該沒問題。整數編碼方面,Protobuf 是 Jackson 的 3 倍。但是和 DSL-JSON 比起來,好像沒有快很多。
這是因為 DSL-JSON 使用了自己的優化方式,和 JDK 的官方實現不一樣
點擊圖片可以放大瀏覽
這段代碼的意思是比較令人費解的。不知道哪裏就做了數字到字符串的轉換了。過程是這樣的,假設輸入了19823,會被分解為 19 和 823 兩部分。然後有一個 `DIGITS` 的查找表,根據這個表把 19 翻譯為 "19",把 823 翻譯為 "823"。其中 "823" 並不是三個byte分開來存的,而是把 bit 放到了一個integer裏,然後在 writeBuf 的時候通過位移把對應的三個byte解開的
點擊圖片可以放大瀏覽
這個實現比 JDK 自帶的 Integer.toString 更快。因為查找表預先計算好了,節省了運行時的計算成本。
Decode Double
解析 JSON 的 Double 就更慢了。
庫 | 相比 Jackson | ns/op |
Protobuf | 13.75 | 92447.958 |
Thrift | 7.30 | 174052.307 |
Jsoniter | 4.2 | 302453.323 |
Jsoniter () | 3.25 | 390812.895 |
DSL-JSON | 2.53 | 502287.602 |
Fastjson | 1.2 | 1055454.855 |
Jackson | 1 | 1271311.735 |
Protobuf 解析 double 是 Jackson 的 13 倍。毫無疑問,JSON 真的不適合存浮點數。
DSL-Json 中對 Double 也是做了特別優化的
點擊圖片可以放大瀏覽
浮點數被去掉了點,存成了 long 類型,然後再除以對應的 10 的倍數。如果輸入是 3.1415,則會變成 31415/10000。
Encode Double
把 double 編碼為文本格式就更困難了。
庫 | 相比 Jackson | ns/op |
Protobuf | 12.71 | 143346.157 |
Thrift | 0.87 | 2093533.015 |
Jsoniter (6 digits) | 6.5 | 280252.226 |
Jsoniter () | 6.68 | 272843.205 |
DSL-JSON | 1.23 | 1483965.621 |
Fastjson | 1.06 | 1722392.219 |
Jackson | 1 | 1822478.053 |
解碼 double 的時候,Protobuf 是 Jackson 的 13 倍。如果你願意犧牲精度的話,可以選擇只保留 6 位小數。在這個取舍下,可以好一些,但是 Protobuf 仍然是 的兩倍。
保留 6 位小數的代碼是這樣寫的。把 double 的處理變成了長整數的處理。
點擊圖片可以放大瀏覽
到目前來看,我們可以說 JSON 不是為數字設計的。如果你使用的是 Jackson,切換到 Protobuf 的話可以把數字的處理速度提高 10 倍。然而 DSL-Json 做的優化可以把這個性能差距大幅縮小,解碼在 3x ~ 4x 之間,編碼在 1.3x ~ 2x 之間(前提是犧牲 double 的編碼精度)。
因為 JSON 處理 double 非常慢。所以 提供了一種把 double 的 IEEE 754 的二進制表示(64個bit)用 編碼之後保存的方案。如果希望提高速度,但是又要保持精度,可以使用 FloatSupport.enableEncodersAndDecoders();
點擊圖片可以放大瀏覽
對於 0.123456789 就變成了 "OWNfmt03P78"
Decode Object
我們已經看到了 JSON 在處理數字方面的笨拙醜態了。在處理對象綁定方面,是不是也一樣不堪?前面的 benchmark 結果那麽差和按字段做綁定是不是有關系?畢竟我們有 10 個字段要處理那。這就來看看在處理字段方面的效率問題。
為了讓比較起來公平一些,我們使用很短的 ascii 編碼的字符串作為字段的值。這樣字符串拷貝的成本大家都差不到哪裏去。所以性能上要有差距,必然是和按字段綁定值有關系。
庫 | 相比 Jackson | ns/op |
Protobuf | 2.52 | 68666.658 |
Thrift | 2.74 | 63139.324 |
Jsoniter | 5.78 | 29887.361 |
DSL-JSON | 5.32 | 32458.030 |
Fastjson | 1.71 | 101107.721 |
Jackson | 1 | 172747.146 |
如果只有一個字段,Protobuf 是 Jackson 的 2.5 倍。但是比 DSL-JSON 要慢。
我們再把同樣的實驗重復幾次,分別對應 5 個字段,10個字段的情況。
庫 | 相比 Jackson | ns/op |
Protobuf | 1.3 | 276972.857 |
Thrift | 1.44 | 250016.572 |
Jsoniter | 2.5 | 143807.401 |
DSL-JSON | 2.41 | 149261.728 |
Fastjson | 1.39 | 259296.397 |
Jackson | 1 | 359868.351 |
在有 5 個字段的情況下,Protobuf 僅僅是 Jackson 的 1.3x 倍。如果你認為 JSON 對象綁定很慢,而且會決定 JSON 解析的整體性能。對不起,你錯了。
庫 | 相比 Jackson | ns/op |
Protobuf | 1.22 | 462167.920 |
Thrift | 1.12 | 503725.605 |
Jsoniter | 2.04 | 277531.128 |
DSL-JSON | 1.84 | 307569.103 |
Fastjson | 1.18 | 477492.445 |
Jackson | 1 | 564942.726 |
把字段數量加到了 10 個之後,Protobuf 僅僅是 Jackson 的 1.22 倍了。看到這裏,你應該懂了吧。
Protobuf 在處理字段綁定的時候,用的是 switch case:
點擊圖片可以放大瀏覽
這個實現比 Hashmap 來說,僅僅是稍微略快而已。DSL-JSON 的實現是先 hash,然後也是類似的分發的方式:
點擊圖片可以放大瀏覽
使用的 hash 算法是 FNV-1a。
是 hash 就會碰撞,所以用起來需要小心。如果輸入很有可能包含未知的字段,則需要放棄速度選擇匹配之後再查一下字段是不是嚴格相等的。有一個解碼模式
DYNAMIC_MODE_AND_MATCH_FIELD_STRICTLY,它可以產生下面這樣的嚴格匹配的代碼:
點擊圖片可以放大瀏覽
即便是嚴格匹配,速度上也是有保證的。DSL-JSON 也有選項,可以在 hash 匹配之後額外加一次字符串 equals 檢查。
庫 | 相比 Jackson | ns/op |
Jsoniter (hash mode) | 2.13 | 274949.346 |
Jsoniter (strict mode) | 1.95 | 300524.989 |
DSL-JSON (hash mode) | 1.91 | 305812.208 |
DSL-JSON (strict mode) | 1.71 | 343203.344 |
Jackson | 1 | 585421.314 |
關於對象綁定來說,只要字段名不長,基於數字的 tag 分發並不會比 JSON 具有明顯優勢,即便是相比最慢的 Jackson 來說也是如此。
Encode Object
廢話不多說了,直接比較一下三種字段數量情況下,編碼的速度
只有 1 個字段
庫 | 相比 Jackson | ns/op |
Protobuf | 1.22 | 57502.775 |
Thrift | 0.86 | 137094.627 |
Jsoniter | 2.06 | 57081.756 |
DSL-JSON | 2.46 | 47890.664 |
Fastjson | 0.92 | 127421.715 |
Jackson | 1 | 117604.479 |
有 5 個字段
庫 | 相比 Jackson | ns/op |
Protobuf | 1.68 | 127933.179 |
Thrift | 0.46 | 467818.566 |
Jsoniter | 2.54 | 84702.001 |
DSL-JSON | 2.68 | 80211.517 |
Fastjson | 0.98 | 219373.346 |
Jackson | 1 | 214802.686 |
有 10 個字段
庫 | 相比 Jackson | ns/op |
Protobuf | 1.72 | 194371.476 |
Thrift | 0.38 | 888230.783 |
Jsoniter | 2.59 | 129305.086 |
DSL-JSON | 2.56 | 130379.967 |
Fastjson | 1.06 | 315267.365 |
Jackson | 1 | 334297.953 |
對象編碼方面,Protobuf 是 Jackson 的 1.7 倍。但是速度其實比 DSL-Json 還要慢。
優化對象編碼的方式是,一次性盡可能多的把控制類的字節寫出去。
點擊圖片可以放大瀏覽
可以看到我們把 "field1": 作為一個整體寫出去了。如果我們知道字段是非空的,則可以進一步的把字符串的雙引號也一起合並寫出去。
點擊圖片可以放大瀏覽
從對象的編解碼的 benchmark 結果可以看出,Protobuf 在這個方面僅僅比 Jackson 略微強一些,而比 DSL-Json 要慢。
Decode Integer List
Protobuf 對於整數列表有特別的支持,可以打包存儲
設置 [packed=true]
庫 | 相比 Jackson | ns/op |
Protobuf | 2.92 | 249888.105 |
Thrift | 3.63 | 201439.691 |
Jsoniter | 2.97 | 245837.298 |
DSL-JSON | 1.97 | 370897.998 |
Fastjson | 0.89 | 820099.921 |
Jackson | 1 | 730450.607 |
對於整數列表的解碼,Protobuf 是 Jackson 的 3 倍。然而比 DSL-Json 的優勢並不明顯。
在 裏,解碼的循環被展開了:
點擊圖片可以放大瀏覽
對於成員比較少的情況,這樣搞可以避免數組的擴容帶來的內存拷貝。
Encode Integer List
Protobuf 在編碼數組的時候應該有優勢,不用寫那麽多逗號出來嘛。
庫 | 相比 Jackson | ns/op |
Protobuf | 1.35 | 159337.360 |
Thrift | 0.45 | 472555.572 |
Jsoniter | 1.9 | 112770.811 |
DSL-JSON | 2.19 | 97998.250 |
Fastjson | 0.66 | 323194.122 |
Jackson | 1 | 214409.223 |
Protobuf 在編碼整數列表的時候,僅僅是 Jackson 的 1.35 倍。雖然 Protobuf 在處理對象的整數字段的時候優勢明顯,但是在處理整數的列表時卻不是如此。在這個方面,DSL-Json 沒有特殊的優化,性能的提高純粹只是因為單個數字的編碼速度提高了。
Decode Object List
列表經常用做對象的容器。測試這種兩種容器組合嵌套的場景,也很有代表意義。
庫 | 相比 Jackson | ns/op |
Protobuf | 1.26 | 1118704.310 |
Thrift | 1.3 | 1078278.555 |
Jsoniter | 2.91 | 483304.365 |
DSL-JSON | 2.22 | 635179.183 |
Fastjson | 1.12 | 1260390.104 |
Jackson | 1 | 1407116.476 |
Protobuf 處理對象列表是 Jackson 的 1.3 倍。但是不及 DSL-JSON。
Encode Object List
庫 | 相比 Jackson | ns/op |
Protobuf | 2.22 | 328219.768 |
Thrift | 0.38 | 1885052.964 |
Jsoniter | 3.63 | 200420.923 |
DSL-JSON | 3.87 | 187964.594 |
Fastjson | 0.85 | 857771.520 |
Jackson | 1 | 727582.950 |
Protobuf 處理對象列表的編碼速度是 Jackson 的 2 倍。但是 DSL-JSON 仍然比 Protobuf 更快。似乎 Protobuf 在處理列表的編碼解碼方面優勢不明顯。
Decode Double Array
Java 的數組有點特殊,double[]是比 List<Double>更高效的。使用 double 數組來代表時間點上的值或者坐標是非常常見的做法。然而,Protobuf 的 Java 庫沒有提供 double[]的支持,repeated 總是使用 List<Double>。我們可以預期 JSON 庫在這裏有一定的優勢。
庫 | 相比 Jackson | ns/op |
Protobuf | 5.18 | 207503.316 |
Thrift | 6.12 | 175678.703 |
Jsoniter | 4.83 | 222818.772 |
Jsoniter () | 3.63 | 296262.142 |
DSL-JSON | 2.8 | 383549.289 |
Fastjson | 0.58 | 1866460.535 |
Jackson | 1 | 1075423.265 |
Protobuf 在處理 double 數組方面,Jackson 與之的差距被縮小為 5 倍。Protobuf 與 DSL-JSON 相比,優勢已經不明顯了。所以如果你有很多的 double 數值需要處理,這些數值必須是在對象的字段上,才會引起性能的巨大差別,對於數組裏的 double,優勢差距被縮小。
在 裏,處理數組的循環也是被展開的。
點擊圖片可以放大瀏覽
這避免了數組擴容的開銷。
Encode Double Array
再來看看 double 數組的編碼
庫 | 相比 Jackson | ns/op |
Protobuf | 15.63 | 107760.788 |
Thrift | 0.54 | 3125678.472 |
Jsoniter (6 digits) | 6.74 | 249945.866 |
Jsoniter () | 7.11 | 236991.658 |
DSL-JSON | 1.14 | 1478332.248 |
Fastjson | 1.08 | 1562377.465 |
Jackson | 1 | 1684935.837 |
Protobuf 可以飛快地對 double 數組進行編碼,是 Jackson 的 15 倍。在犧牲精度的情況下,Protobuf 只是 的 2.3 倍。所以,再次證明了,JSON 處理 double 非常慢。如果用 編碼 double,則可以保持精度,速度和犧牲精度時一樣。
Decode String
JSON 字符串包含了轉義字符的支持。Protobuf 解碼字符串僅僅是一個內存拷貝。理應更快才對。被測試的字符串長度是 160 個字節的 ascii。
庫 | 相比 Jackson | ns/op |
Protobuf | 1.85 | 173680.548 |
Thrift | 2.29 | 140635.170 |
Jsoniter | 2.4 | 134067.924 |
DSL-JSON | 2.27 | 141419.108 |
Fastjson | 1.14 | 281061.212 |
Jackson | 1 | 321406.155 |
Protobuf 解碼長字符串是 Jackson 的 1.85 倍。然而,DSL-Json 比 Protobuf 更快。這就有點奇怪了,JSON 的處理負擔更重,為什麽會更快呢?
先嘗試捷徑
DSL-JSON 給 ascii 實現了一個捷徑:
點擊圖片可以放大瀏覽
這個捷徑裏規避了處理轉義字符和utf8字符串的成本。
JVM 的動態編譯做了特殊優化
在 JDK9 之前,java.lang.String 都是基於 `char[]` 的。而輸入都是 byte[]並且是 utf-8 編碼的。所以這使得,我們不能直接用 memcpy 的方式來處理字符串的解碼問題。
但是在 JDK9 裏,java.lang.String 已經改成了基於`byte[]`的了。從 JDK9 的源代碼裏可以看出:
點擊圖片可以放大瀏覽
使用這個雖然被廢棄,但是還沒有被刪除的構造函數,我們可以使用 Arrays.copyOfRange 來直接構造 java.lang.String 了。然而,在測試之後,發現這個實現方式並沒有比 DSL-JSON 的實現更快。
似乎 JVM 的 Hotspot 動態編譯時對這段循環的代碼做了模式匹配,識別出了更高效的實現方式。即便是在 JDK9 使用 +UseCompactStrings 的前提下,理論上來說本應該更慢的 byte[] => char[] => byte[] 並沒有使得這段代碼變慢,DSL-JSON 的實現還是最快的。
如果輸入大部分是字符串,這個優化就變得至關重要了。Java 裏的解析藝術,還不如說是字節拷貝的藝術。JVM 的 java.lang.String 設計實在是太愚蠢了。在現代一點的語言中,比如 Go,字符串都是基於 utf-8 byte[]的。
Encode String
類似的問題,因為需要把 char[] 轉換為 byte[],所以沒法直接內存拷貝。
庫 | 相比 Jackson | ns/op |
Protobuf | 0.96 | 262077.921 |
Thrift | 0.99 | 252140.935 |
Jsoniter | 1.5 | 166381.978 |
DSL-JSON | 1.38 | 181008.120 |
Fastjson | 0.74 | 339919.707 |
Jackson | 1 | 250431.354 |
Protobuf 在編碼長字符串時,比 Jackson 略微快一點點。一切都歸咎於 char[]。
跳過數據結構
JSON 是一個沒有 header 的格式。因為沒有 header,JSON 需要掃描每個字節才可以定位到所需的字段上。中間可能要掃過很多不需要處理的字段。
消息用 PbTestWriteObject 來編碼,然後用 PbTestReadObject 來解碼。field1 和 field2 的內容應該被跳過。
庫 | 相比 Jackson | ns/op |
Protobuf | 5.05 | 152194.483 |
Thrift | 5.43 | 141467.209 |
Jsoniter | 3.75 | 204704.100 |
DSL-JSON | 2.51 | 305784.845 |
Fastjson | 0.4 | 1949277.734 |
Jackson | 1 | 768840.597 |
Protobuf 在跳過數據結構方面,是 Jackson 的 5 倍。但是如果跳過長的字符串,JSON 的成本是和字符串長度線性相關的,而 Protobuf 則是一個常量操作。
總結
最後,我們把所有的戰果匯總到一起。
場景 | Protobuf V.S. Jackson | Protobuf V.S. Jsoniter | Jsoniter V.S Jackson |
Decode Integer | 8.51 | 2.64 | 3.22 |
Encode Integer | 2.9 | 1.44 | 2.02 |
Decode Double | 13.75 | 3.27 | 4.2 |
Encode Double | 12.71 | 1.96 (只保留小數點後6位) | 6.5 |
Decode Object | 1.22 | 0.6 | 2.04 |
Encode Object | 1.72 | 0.67 | 2.59 |
Decode Integer List | 2.92 | 0.98 | 2.97 |
Encode Integer List | 1.35 | 0.71 | 1.9 |
Decode Object List | 1.26 | 0.43 | 2.91 |
Encode Object List | 2.22 | 0.61 | 3.63 |
Decode Double Array | 5.18 | 1.47 | 4.83 |
Encode Double Array | 15.63 | 2.32 (只保留小數點後6位) | 6.74 |
Decode String | 1.85 | 0.77 | 2.4 |
Encode String | 0.96 | 0.63 | 1.5 |
Skip Structure | 5.05 | 1.35 | 3.75 |
編解碼數字的時候,JSON 仍然是非常慢的。把這個差距從 10 倍縮小到了 3 倍多一些。
JSON 最差的情況是下面幾種:
-
跳過非常長的字符串:和字符串長度線性相關。
-
解碼 double 字段:Protobuf 優勢明顯,是 的 3.27 倍,是 Jackson 的 13.75 倍。
-
編碼 double 字段:如果不能接受只保留 6 位小數,Protobuf 是 Jackson 的 12.71 倍。如果接受精度損失,Protobuf 是 的 1.96 倍。
-
解碼整數:Protobuf 是 的 2.64 倍,是 Jackson 的 8.51 倍。
如果你的生產環境中的 JSON 沒有那麽多的 double 字段,都是字符串占大頭,那麽基本上來說替換成 Protobuf 也就是僅僅比 提高一點點,肯定在 2 倍之內。如果不幸的話,沒準 Protobuf 還要更慢一點。
Protobuf有沒有比JSON快5倍?用代碼來擊破pb性能神話