1. 程式人生 > >Protobuf有沒有比JSON快5倍?用代碼來擊破pb性能神話

Protobuf有沒有比JSON快5倍?用代碼來擊破pb性能神話

字段名 觀點 5.1 mod 就會 skip 高速 div ngs

轉 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性能神話