數(shù)千字長(zhǎng)文預(yù)警
背景
JSON/XML不好嗎?
好昧穿,再?zèng)]有一種序列化方案能像JSON和XML一樣流行,自由橙喘、方便时鸵,擁有強(qiáng)大的表達(dá)力和跨平臺(tái)能力。是通用數(shù)據(jù)傳輸格式的默認(rèn)首選渴杆。不過隨著數(shù)據(jù)量的增加和性能要求的提升寥枝,這種自由與通用帶來(lái)的性能問題也不容忽視宪塔。
JSON和XML使用字符串表示所有的數(shù)據(jù),對(duì)于非字符數(shù)據(jù)來(lái)說囊拜,字面量表達(dá)會(huì)占用很多額外的存儲(chǔ)空間某筐,并且會(huì)嚴(yán)重受到數(shù)值大小和精度的影響。 一個(gè)32位浮點(diǎn)數(shù) 1234.5678 在內(nèi)存中占用 4 bytes 空間冠跷,如果存儲(chǔ)為 utf8 南誊,則需要占用 9 bytes空間,在JS這樣使用utf16表達(dá)字符串的環(huán)境中蜜托,需要占用 18 bytes空間抄囚。 使用正則表達(dá)式進(jìn)行數(shù)據(jù)解析,在面對(duì)非字符數(shù)據(jù)時(shí)顯得十分低效橄务,不僅要耗費(fèi)大量的運(yùn)算解析數(shù)據(jù)結(jié)構(gòu)幔托,還要將字面量轉(zhuǎn)換成對(duì)應(yīng)的數(shù)據(jù)類型。
在面對(duì)海量數(shù)據(jù)時(shí)蜂挪,這種格式本身就能夠成為整個(gè)系統(tǒng)的IO與計(jì)算瓶頸重挑,甚至直接overflow。
JSON/XML之外還有什么?
眾多的序列化方案中棠涮,按照存儲(chǔ)方案谬哀,可分為字符串存儲(chǔ)和二進(jìn)制存儲(chǔ),字符串存儲(chǔ)是可讀的严肪,但是由于以上問題史煎,這里只考慮二進(jìn)制存儲(chǔ)。二進(jìn)制存儲(chǔ)中可分為需要IDL和不需要IDL驳糯,或分為自描述與非自描述(反序列化是否需要IDL)篇梭。
需要IDL的使用過程:
- 使用該方案所定義的IDL語(yǔ)法編寫schema
- 使用該方案提供的編譯器將schema編譯成生產(chǎn)方和消費(fèi)方所用語(yǔ)言的代碼(類或者模塊)
- 數(shù)據(jù)生產(chǎn)方引用該代碼,根據(jù)其接口结窘,構(gòu)建數(shù)據(jù)很洋,并序列化
- 消費(fèi)方引用該代碼,根據(jù)其接口讀取數(shù)據(jù)
不需要IDL的使用過程:
- 生產(chǎn)方與消費(fèi)方通過文檔約定數(shù)據(jù)結(jié)構(gòu)
- 生產(chǎn)方序列化
- 消費(fèi)方反序列化
etc.
-
protocol buffers
- gRPC所用的傳輸協(xié)議隧枫,二進(jìn)制存儲(chǔ)喉磁,需要IDL,非自描述
- 高壓縮率官脓,表達(dá)性極強(qiáng)协怒,在Google系產(chǎn)品中使用廣泛
-
flat buffers
- Google推出序列化方案,二進(jìn)制存儲(chǔ)卑笨,需要IDL孕暇,非自描述(自描述方案不跨平臺(tái))
- 高性能,體積小,支持string妖滔、number隧哮、boolean
-
avro
- Hadoop使用的序列化方案,將二進(jìn)制方案和字符串方案的優(yōu)勢(shì)結(jié)合起來(lái)座舍,僅序列化過程需要IDL沮翔,自描述
- 然而場(chǎng)景受限,沒有成熟的JS實(shí)現(xiàn)曲秉,不適合Web環(huán)境采蚀,這里不做對(duì)比
-
Thrift
- Facebook的方案,二進(jìn)制存儲(chǔ)承二,需要IDL榆鼠,非自描述
- 基本上只集成在RPC中使用,這里不做對(duì)比
-
- 針對(duì)多維數(shù)組設(shè)計(jì)的序列化方案亥鸠,二進(jìn)制存儲(chǔ)妆够,不需要IDL,自描述
- 高性能负蚊,體積小责静,支持string、number、boolean
優(yōu)化原理
空間優(yōu)化原理
使用數(shù)值類型而非字面量來(lái)保存數(shù)值,本身就能節(jié)約一筆十分可觀的空間匹舞。 protocol buffer為了實(shí)現(xiàn)更高的壓縮率平夜,使用varint去壓縮數(shù)值。(不過下面的測(cè)試表明嵌赠,可以使用gzip的環(huán)境中塑荒,這種方案沒有幫助)
時(shí)間優(yōu)化原理
二進(jìn)制格式用通過特定位置來(lái)記錄數(shù)據(jù)結(jié)構(gòu)以及每個(gè)節(jié)點(diǎn)數(shù)據(jù)的偏移量,省去了從字符串中解析數(shù)據(jù)結(jié)構(gòu)所耗費(fèi)的時(shí)間姜挺,避免了長(zhǎng)字符串帶來(lái)的性能問題齿税,在GC語(yǔ)言中,也大大減少了中間垃圾的產(chǎn)生炊豪。
在可以進(jìn)行內(nèi)存直接操作的環(huán)境中(包括JS)凌箕,還可以通過內(nèi)存偏移量直接讀取數(shù)據(jù),而避免進(jìn)行復(fù)制操作词渤,也避免開辟額外的內(nèi)存空間牵舱。DIMBIN和flatbuffers都使用了這種理念來(lái)優(yōu)化數(shù)據(jù)存儲(chǔ)性能。在JS環(huán)境中缺虐,通過建立DataView或者TypedArray來(lái)從內(nèi)存段中提取數(shù)據(jù)的耗時(shí)基本上可以忽略不計(jì)芜壁。
二進(jìn)制方案中存儲(chǔ)字符串需要額外的邏輯進(jìn)行UTF8編解碼,性能和體積不如JSON這樣的字符串格式。
DIMBIN是什么慧妄?
我們的數(shù)據(jù)可視化場(chǎng)景中經(jīng)常涉及百萬(wàn)甚至千萬(wàn)條數(shù)據(jù)的實(shí)時(shí)更新顷牌,為解決JSON的性能問題,我們使用內(nèi)存偏移量操作的思路塞淹,開發(fā)了DIMBIN作為序列化方案窟蓝,并基于其上設(shè)計(jì)了許多針對(duì)web端數(shù)據(jù)處理的傳輸格式。
作為一種簡(jiǎn)單直白的優(yōu)化思路窖铡,DIMBIN已經(jīng)成為我們數(shù)據(jù)傳輸?shù)臉?biāo)準(zhǔn)方案疗锐,保持絕對(duì)的精簡(jiǎn)與高效。
我們剛剛將DIMBIN開源费彼,貢獻(xiàn)給社區(qū)滑臊,希望能為大家?guī)?lái)一個(gè)比 JSON/protocol/flatbuffers 更輕、更快箍铲、對(duì)Web更友好的解決方案雇卷。
方案對(duì)比
針對(duì)Web/JS環(huán)境中的使用,我們選擇 JSON颠猴、protocol buffers关划、flat buffers、DIMBIN 四種方案翘瓮,從七個(gè)方面進(jìn)行對(duì)比贮折。
工程化
Protocolbuffers 和 flatbuffers 代表Google所倡導(dǎo)的完整的workflow。嚴(yán)格资盅、規(guī)范调榄、統(tǒng)一、面向IDL呵扛,為多端協(xié)作所設(shè)計(jì)每庆,針對(duì)python/java/c++。通過IDL生成代碼今穿,多平臺(tái)/多語(yǔ)言使用一致的開發(fā)流程缤灵。如果團(tuán)隊(duì)采用這種工作流,那么這種方案更便于管理蓝晒,多端協(xié)作和接口更迭都更可控腮出。
但是如果離開了這套工程結(jié)構(gòu),則顯得相對(duì)繁雜拔创。
JSON/XML 和 DIMBIN 是中立的利诺,不需要IDL,不對(duì)工程化方案和技術(shù)選型作假設(shè)或限制剩燥÷猓可以只通過文檔規(guī)范接口立倍,也可以自行添加schema約束。
部署/編碼復(fù)雜度
Protocolbuffers 和 flatbuffers 須在項(xiàng)目設(shè)計(jì)的早期階段加入侣滩,并作為工作流中的關(guān)鍵環(huán)節(jié)口注。如果出于性能優(yōu)化目的而加入,會(huì)對(duì)項(xiàng)目架構(gòu)造成較大影響君珠。
JSON基本是所有平臺(tái)的基礎(chǔ)設(shè)施寝志,無(wú)部署成本。
DIMBIN只需要安裝一個(gè)軟件包策添,但是需要數(shù)據(jù)結(jié)構(gòu)扁平化材部,如果數(shù)據(jù)結(jié)構(gòu)無(wú)法被扁平化,將無(wú)法從中受益唯竹。
在JS中使用時(shí):
使用JSON序列化反序列化的代碼行數(shù)基本在5以內(nèi)
使用DIMBIN則10行左右
使用protocol需要單獨(dú)編寫schema(proto)文件乐导,引入編譯出的幾百行代碼,序列化和反序列化時(shí)浸颓,需要通過面向?qū)ο箫L(fēng)格的接口操作每個(gè)節(jié)點(diǎn)的數(shù)據(jù)(數(shù)據(jù)結(jié)構(gòu)上的每個(gè)節(jié)點(diǎn)都是一個(gè)對(duì)象)
使用flatbuffer需要單獨(dú)編寫schema(fbs)文件物臂,引入編譯出的幾百行代碼,序列化過程需要通過狀態(tài)機(jī)風(fēng)格的接口處理每個(gè)節(jié)點(diǎn)产上,手動(dòng)轉(zhuǎn)換并放入每個(gè)節(jié)點(diǎn)的數(shù)據(jù)棵磷,書寫體驗(yàn)比較磨人;反序列化過程通過對(duì)象操作接口讀取每個(gè)節(jié)點(diǎn)的數(shù)據(jù)
性能(JS環(huán)境)
Protocol官網(wǎng)聲稱性能高于JSON晋涣,該測(cè)試數(shù)據(jù)顯然不是JS端的仪媒,我們的測(cè)試表明其JS端性相對(duì)于JSON更差(數(shù)據(jù)量大的時(shí)候差的多)。
所有的二進(jìn)制方案處理字符串的過程都是類似的:需要將js中的utf16先解碼成unicode谢鹊,再編碼成utf8规丽,寫入buffer,并記錄每個(gè)字符串的數(shù)據(jù)地址撇贺。該過程性能消耗較大,而且如果不使用varint(protocol buffers)的話冰抢,體積也沒有任何優(yōu)勢(shì)松嘶。
在處理字符串?dāng)?shù)據(jù)時(shí),JSON的性能總是最好的挎扰,序列化性能 JSON > DIMBIN > flatbuffers > proto翠订,反序列化 JSON > proto > DIMBIN > flatbuffers
處理數(shù)值數(shù)據(jù)時(shí) Flatbuffers 和 DIMBIN 性能優(yōu)勢(shì)明顯,
對(duì)于扁平化數(shù)值數(shù)據(jù)的序列化性能 DIMBIN > flatbuffers > JSON > proto遵倦,
反序列化 DIMBIN > flatbuffers >十萬(wàn)倍> JSON > proto
體積
使用字符串與數(shù)值混合結(jié)構(gòu)或者純數(shù)值時(shí)尽超,protocol < DIMBIN < flat < JSON 使用純字符串時(shí),JSON最小梧躺,二進(jìn)制方案都比較大
Gzip之后似谁,DIMBIN和flat的體積最小且基本一致,protocol反而沒有優(yōu)勢(shì)秃诵,猜測(cè)可能是varint的副作用塞琼。
表達(dá)力
Protocol 為強(qiáng)類型語(yǔ)言而設(shè)計(jì),所支持的類型比JSON要豐富的多彪杉,數(shù)據(jù)結(jié)構(gòu)也可以十分復(fù)雜; Flatbuffers 支持 數(shù)值/布爾值/字符串 三種基本類型攀唯,結(jié)構(gòu)與JSON類似构哺; DIMBIN 支持 數(shù)值/布爾值/字符串 三種基本類型,目前只支持多維數(shù)組的結(jié)構(gòu)(暫不支持也不鼓勵(lì)使用鍵值對(duì))曙强,更復(fù)雜的結(jié)構(gòu)需要在其上封裝。
自由度
JSON和DIMBIN都是自描述的溪食,(弱類型語(yǔ)言中)不需要schema,用戶可以動(dòng)態(tài)生成數(shù)據(jù)結(jié)構(gòu)和數(shù)據(jù)類型娜扇,生產(chǎn)方和消費(fèi)方之間約定好即可错沃,如果需要類型檢查則需要在上層封裝。
Protocolbuffers 和 flatbuffers 必須在編碼前先寫好IDL并生成對(duì)應(yīng)的代碼雀瓢,接口修改則需修改IDL并重新生成代碼枢析、部署到生產(chǎn)端和消費(fèi)端、再基于其上進(jìn)行編碼刃麸。
Protocolbuffers的C++和java實(shí)現(xiàn)中有自描述的特性醒叁,可以嵌入.proto文件,但是依然需要編譯一個(gè)頂層接口來(lái)描述這個(gè)“自描述的內(nèi)嵌數(shù)據(jù)”泊业,基本沒有實(shí)用性把沼,其文檔中也說Google內(nèi)部從來(lái)沒有這樣用過(不符合IDL的設(shè)計(jì)原則)。
flatbuffers 有一個(gè)自描述版本的分支(flexbuffers)吁伺,試驗(yàn)階段饮睬,無(wú)JS支持,無(wú)相關(guān)文檔篮奄。
多語(yǔ)言支持
Protocolbuffers 和 flatbuffers 服務(wù)端與客戶端語(yǔ)言支持都非常完整捆愁。兩者優(yōu)先針對(duì)C++/Java(android)/Python開發(fā)割去,JS端缺少一部分高級(jí)功能,無(wú)完整文檔牙瓢,需要自己研究example和生成的代碼劫拗,不過代碼不長(zhǎng),注釋覆蓋完整矾克。
JSON基本上所有編程語(yǔ)言都有對(duì)應(yīng)的工具页慷。
DIMBIN針對(duì)JS/TS開發(fā)和優(yōu)化,目前提供c#版本胁附,c++酒繁、wasm、java和python的支持在計(jì)劃中州袒。
用例(僅測(cè)試JS環(huán)境)
我們生成一份典型的數(shù)據(jù)郎哭,使用扁平化和非扁平化兩種結(jié)構(gòu)夸研,使用JSON、DIMBIN姐扮、protocol和flat buffers來(lái)實(shí)現(xiàn)相同的功能茶敏,對(duì)比各種方案的性能睡榆、體積以及便捷程度。
測(cè)試數(shù)據(jù)
我們生成兩個(gè)版本的測(cè)試數(shù)據(jù):非扁平化(多層鍵值對(duì)結(jié)構(gòu))數(shù)據(jù)和等效的扁平化(多維數(shù)組)數(shù)據(jù)
考慮到字符串處理的特殊性包雀,在測(cè)試時(shí)我們分開測(cè)試了 字符串/數(shù)值混合數(shù)據(jù)才写、純字符串?dāng)?shù)據(jù)赞草,和純數(shù)值數(shù)據(jù)
// 非扁平化數(shù)據(jù)
export const data = {
items: [
{
position: [0, 0, 0],
index: 0,
info: {
a: 'text text text...',
b: 10.12,
},
},
// * 200,000 個(gè)
],
}
// 等效的扁平化數(shù)據(jù)
export const flattedData = {
positions: [0, 0, 0, 0, 0, 1, ...],
indices: [0, 1, ...],
info_a: ['text text text', 'text', ...],
info_b: [10.12, 12.04, ...],
}
JSON
序列化
const jsonSerialize = () => {
return JSON.stringify(data)
}
反序列化
const jsonParse = str => {
const _data = JSON.parse(str)
let _read = null
// 由于flat buffers的讀取操作是延后的洲守,因此這里需要主動(dòng)讀取數(shù)據(jù)來(lái)保證測(cè)試的公平性
const len = _data.items.length
for (let i = 0; i < len; i++) {
const item = _data.items[i]
_read = item.info.a
_read = item.info.b
_read = item.index
_read = item.position
}
}
DIMBIN
序列化
import DIMBIN from 'src/dimbin'
const dimbinSerialize = () => {
return DIMBIN.serialize([
new Float32Array(flattedData.positions),
new Int32Array(flattedData.indices),
DIMBIN.stringsSerialize(flattedData.info_a),
new Float32Array(flattedData.info_b),
])
}
反序列化
const dimbinParse = buffer => {
const dim = DIMBIN.parse(buffer)
const result = {
positions: dim[0],
indices: dim[1],
info_a: DIMBIN.stringsParse(dim[2]),
info_b: dim[3],
}
}
DIMBIN目前僅支持多維數(shù)組,不能處理樹狀數(shù)據(jù)結(jié)構(gòu)叙谨,這里不做對(duì)比手负。
Protocol Buffers
schema
首先需要按照proto3語(yǔ)法編寫schema
syntax = "proto3";
message Info {
string a = 1;
float b = 2;
}
message Item {
repeated float position = 1;
int32 index = 2;
Info info = 3;
}
message Data {
repeated Item items = 1;
}
message FlattedData {
repeated float positions = 1;
repeated int32 indices = 2;
repeated string info_a = 3;
repeated float info_b = 4;
}
編譯成js
使用 protoc 編譯器將schema編譯成JS模塊
<pre style="margin: 0px; padding: 0px; list-style-type: none; list-style-image: none; font-family: "Courier New", Courier, monospace; color: rgb(68, 68, 68); font-size: 14px; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-align: start; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; background-color: rgb(255, 255, 255); text-decoration-style: initial; text-decoration-color: initial;"> ./lib/protoc-3.8.0-osx-x86_64/bin/protoc ./src/data.proto --js_out=import_style=commonjs,,binary:./src/generated</pre>
序列化
// 引入編譯好的JS模塊
const messages = require('src/generated/src/data_pb.js')
const protoSerialize = () => {
// 頂層節(jié)點(diǎn)
const pbData = new messages.Data()
data.items.forEach(item => {
// 節(jié)點(diǎn)
const pbInfo = new messages.Info()
// 節(jié)點(diǎn)寫入數(shù)據(jù)
pbInfo.setA(item.info.a)
pbInfo.setB(item.info.b)
// 子級(jí)節(jié)點(diǎn)
const pbItem = new messages.Item()
pbItem.setInfo(pbInfo)
pbItem.setIndex(item.index)
pbItem.setPositionList(item.position)
pbData.addItems(pbItem)
})
// 序列化
const buffer = pbData.serializeBinary()
return buffer
// 扁平化方案:
// const pbData = new messages.FlattedData()
// pbData.setPositionsList(flattedData.positions)
// pbData.setIndicesList(flattedData.indices)
// pbData.setInfoAList(flattedData.info_a)
// pbData.setInfoBList(flattedData.info_b)
// const buffer = pbData.serializeBinary()
// return buffer
}
反序列化
// 引入編譯好的JS模塊
const messages = require('src/generated/src/data_pb.js')
const protoParse = buffer => {
const _data = messages.Data.deserializeBinary(buffer)
let _read = null
const items = _data.getItemsList()
for (let i = 0; i < items.length; i++) {
const item = items[i]
const info = item.getInfo()
_read = info.getA()
_read = info.getB()
_read = item.getIndex()
_read = item.getPositionList()
}
// 扁平化方案:
// const _data = messages.FlattedData.deserializeBinary(buffer)
// // 讀數(shù)據(jù)(避免延遲讀取帶來(lái)的標(biāo)定誤差)
// let _read = null
// _read = _data.getPositionsList()
// _read = _data.getIndicesList()
// _read = _data.getInfoAList()
// _read = _data.getInfoBList()
}
Flat buffers
schema
首先需要按照proto3語(yǔ)法編寫schema
<pre style="margin: 0px; padding: 0px; list-style-type: none; list-style-image: none; font-family: "Courier New", Courier, monospace; color: rgb(68, 68, 68); font-size: 14px; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-align: start; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; background-color: rgb(255, 255, 255); text-decoration-style: initial; text-decoration-color: initial;"> table Info {
a: string;
b: float;
}
?
table Item {
position: [float];
index: int;
info: Info;
}
?
table Data {
items: [Item];
}
?
table FlattedData {
positions:[float];
indices:[int];
info_a:[string];
info_b:[float];
}</pre>
編譯成js
<pre style="margin: 0px; padding: 0px; list-style-type: none; list-style-image: none; font-family: "Courier New", Courier, monospace; color: rgb(68, 68, 68); font-size: 14px; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-align: start; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; background-color: rgb(255, 255, 255); text-decoration-style: initial; text-decoration-color: initial;"> ./lib/flatbuffers-1.11.0/flatc -o ./src/generated/ --js --binary ./src/data.fbs</pre>
序列化
// 首先引入基礎(chǔ)庫(kù)
const flatbuffers = require('flatbuffers').flatbuffers
// 然后引入編譯出的JS模塊
const tables = require('src/generated/data_generated.js')
const flatbufferSerialize = () => {
const builder = new flatbuffers.Builder(0)
const items = []
data.items.forEach(item => {
let a = null
// 字符串處理
if (item.info.a) {
a = builder.createString(item.info.a)
}
// 開始操作 info 節(jié)點(diǎn)
tables.Info.startInfo(builder)
// 添加數(shù)值
item.info.a && tables.Info.addA(builder, a)
tables.Info.addB(builder, item.info.b)
// 完成操作info節(jié)點(diǎn)
const fbInfo = tables.Info.endInfo(builder)
// 數(shù)組處理
let position = null
if (item.position) {
position = tables.Item.createPositionVector(builder, item.position)
}
// 開始操作item節(jié)點(diǎn)
tables.Item.startItem(builder)
// 寫入數(shù)據(jù)
item.position && tables.Item.addPosition(builder, position)
item.index && tables.Item.addIndex(builder, item.index)
tables.Item.addInfo(builder, fbInfo)
// 完成info節(jié)點(diǎn)
const fbItem = tables.Item.endItem(builder)
items.push(fbItem)
})
// 數(shù)組處理
const pbItems = tables.Data.createItemsVector(builder, items)
// 開始操作data節(jié)點(diǎn)
tables.Data.startData(builder)
// 寫入數(shù)據(jù)
tables.Data.addItems(builder, pbItems)
// 完成操作
const fbData = tables.Data.endData(builder)
// 完成所有操作
builder.finish(fbData)
// 輸出
// @NOTE 這個(gè)buffer是有偏移量的
// return builder.asUint8Array().buffer
return builder.asUint8Array().slice().buffer
// 扁平化方案:
// const builder = new flatbuffers.Builder(0)
// const pbPositions = tables.FlattedData.createPositionsVector(builder, flattedData.positions)
// const pbIndices = tables.FlattedData.createIndicesVector(builder, flattedData.indices)
// const pbInfoB = tables.FlattedData.createInfoBVector(builder, flattedData.info_b)
// const infoAs = []
// for (let i = 0; i < flattedData.info_a.length; i++) {
// const str = flattedData.info_a[i]
// if (str) {
// const a = builder.createString(str)
// infoAs.push(a)
// }
// }
// const pbInfoA = tables.FlattedData.createInfoAVector(builder, infoAs)
// tables.FlattedData.startFlattedData(builder)
// tables.FlattedData.addPositions(builder, pbPositions)
// tables.FlattedData.addIndices(builder, pbIndices)
// tables.FlattedData.addInfoA(builder, pbInfoA)
// tables.FlattedData.addInfoB(builder, pbInfoB)
// const fbData = tables.FlattedData.endFlattedData(builder)
// builder.finish(fbData)
// // 這個(gè)buffer是有偏移量的
// return builder.asUint8Array().slice().buffer
// // return builder.asUint8Array().buffer
}
反序列化
// 首先引入基礎(chǔ)庫(kù)
const flatbuffers = require('flatbuffers').flatbuffers
// 然后引入編譯出的JS模塊
const tables = require('src/generated/data_generated.js')
const flatbufferParse = buffer => {
buffer = new Uint8Array(buffer)
buffer = new flatbuffers.ByteBuffer(buffer)
const _data = tables.Data.getRootAsData(buffer)
// 讀數(shù)據(jù)(flatbuffer在解析時(shí)并不讀取數(shù)據(jù),因此這里需要主動(dòng)讀)
let _read = null
const len = _data.itemsLength()
for (let i = 0; i < len; i++) {
const item = _data.items(i)
const info = item.info()
_read = info.a()
_read = info.b()
_read = item.index()
_read = item.positionArray()
}
// 扁平化方案:
// buffer = new Uint8Array(buffer)
// buffer = new flatbuffers.ByteBuffer(buffer)
// const _data = tables.FlattedData.getRootAsFlattedData(buffer)
// // 讀數(shù)據(jù)(flatbuffer是使用get函數(shù)延遲讀取的迷守,因此這里需要主動(dòng)讀取數(shù)據(jù))
// let _read = null
// _read = _data.positionsArray()
// _read = _data.indicesArray()
// _read = _data.infoBArray()
// const len = _data.infoALength()
// for (let i = 0; i < len; i++) {
// _read = _data.infoA(i)
// }
}
Flatbuffers 對(duì)字符串的解析性能較差兑凿,當(dāng)數(shù)據(jù)中的字符串占比較高時(shí)礼华,其整體序列化性能圣絮、解析性能和體積都不如JSON捧请,對(duì)于純數(shù)值數(shù)據(jù)疹蛉,相對(duì)于JSON優(yōu)勢(shì)明顯可款。其狀態(tài)機(jī)一般的接口設(shè)計(jì)對(duì)于復(fù)雜數(shù)據(jù)結(jié)構(gòu)的構(gòu)建比較繁瑣筑舅。
性能指標(biāo)
測(cè)試環(huán)境:15' MBP mid 2015翠拣,2.2 GHz Intel Core i7误墓,16 GB 1600 MHz DDR3谜慌,macOS 10.14.3欣范,Chrome 75
測(cè)試數(shù)據(jù):上面例子中的數(shù)據(jù),200,000條屏富,字符串使用 UUID*2
測(cè)試方式:運(yùn)行10次取平均值噩死,GZip使用默認(rèn)配置 gzip ./*
單位:時(shí)間 ms已维,體積 Mb
字符串在數(shù)據(jù)中的占比垛耳、單個(gè)字符串的長(zhǎng)度,以及字符串中unicode的數(shù)值大小占婉,都會(huì)對(duì)測(cè)試造成影響逆济。
由于DIMBIN針對(duì)扁平化數(shù)據(jù)而設(shè)計(jì)奖慌,因此非扁平化數(shù)據(jù)只測(cè)試了JSON/protocol/flatbuffers
序列化性能
反序列化性能
空間占用
選型建議
從測(cè)試結(jié)果來(lái)看棉姐,如果你的場(chǎng)景對(duì)性能有較高要求啦逆,將數(shù)據(jù)扁平化總是明智的原則夏志。
數(shù)據(jù)量小湿诊、快速迭代溉贿、包含大量字符串數(shù)據(jù)宇色,使用JSON例隆,方便快捷抢蚀;
數(shù)據(jù)量小、接口穩(wěn)定吴侦、靜態(tài)語(yǔ)言主導(dǎo)备韧、多語(yǔ)言協(xié)作织堂、集成IDL易阳、依賴gPRC潦俺,考慮 protocol buffers黑竞。
數(shù)據(jù)量大很魂、接口穩(wěn)定遏匆、靜態(tài)語(yǔ)言主導(dǎo)幅聘、集成IDL帝蒿、數(shù)據(jù)無(wú)法扁平化葛超,考慮 flat buffers绣张。
數(shù)據(jù)量大侥涵、快速迭代芜飘、性能要求高嗦明、數(shù)據(jù)可以扁平化,不希望使用重量級(jí)工具或修改工程結(jié)構(gòu)邻薯,考慮DIMBIN厕诡。