序列化方案選型對(duì)比 - JSON/ProtocolBuffer/FlatBuffer/DIMBIN

數(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ì)比
  • DIMBIN

    • 針對(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: &quot;Courier New&quot;, 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厕诡。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末壹罚,一起剝皮案震驚了整個(gè)濱河市猖凛,隨后出現(xiàn)的幾起案子辨泳,更是在濱河造成了極大的恐慌菠红,老刑警劉巖,帶你破解...
    沈念sama閱讀 206,839評(píng)論 6 482
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件遇绞,死亡現(xiàn)場(chǎng)離奇詭異试读,居然都是意外死亡钩骇,警方通過查閱死者的電腦和手機(jī)银亲,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,543評(píng)論 2 382
  • 文/潘曉璐 我一進(jìn)店門务蝠,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)馏段,“玉大人院喜,你說我怎么就攤上這事喷舀×蚵椋” “怎么了?”我有些...
    開封第一講書人閱讀 153,116評(píng)論 0 344
  • 文/不壞的土叔 我叫張陵赶掖,是天一觀的道長(zhǎng)奢赂。 經(jīng)常有香客問我,道長(zhǎng)轧钓,這世上最難降的妖魔是什么锐膜? 我笑而不...
    開封第一講書人閱讀 55,371評(píng)論 1 279
  • 正文 為了忘掉前任毕箍,我火速辦了婚禮,結(jié)果婚禮上道盏,老公的妹妹穿的比我還像新娘而柑。我一直安慰自己文捶,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,384評(píng)論 5 374
  • 文/花漫 我一把揭開白布媒咳。 她就那樣靜靜地躺著粹排,像睡著了一般。 火紅的嫁衣襯著肌膚如雪涩澡。 梳的紋絲不亂的頭發(fā)上射富,一...
    開封第一講書人閱讀 49,111評(píng)論 1 285
  • 那天掂恕,我揣著相機(jī)與錄音店枣,去河邊找鬼。 笑死,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的忽舟。 我是一名探鬼主播枝嘶,決...
    沈念sama閱讀 38,416評(píng)論 3 400
  • 文/蒼蘭香墨 我猛地睜開眼暑劝,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 37,053評(píng)論 0 259
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤狈茉,失蹤者是張志新(化名)和其女友劉穎点晴,沒想到半個(gè)月后族跛,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體桐绒,經(jīng)...
    沈念sama閱讀 43,558評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡派撕,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,007評(píng)論 2 325
  • 正文 我和宋清朗相戀三年蛙粘,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 38,117評(píng)論 1 334
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡想暗,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出尊勿,到底是詐尸還是另有隱情擅羞,我是刑警寧澤,帶...
    沈念sama閱讀 33,756評(píng)論 4 324
  • 正文 年R本政府宣布惕稻,位于F島的核電站腿准,受9級(jí)特大地震影響孟辑,放射性物質(zhì)發(fā)生泄漏榴鼎。R本人自食惡果不足惜心赶,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,324評(píng)論 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸筷狼。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,315評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)滔吠。三九已至只冻,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間计技,已是汗流浹背喜德。 一陣腳步聲響...
    開封第一講書人閱讀 31,539評(píng)論 1 262
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留垮媒,地道東北人舍悯。 一個(gè)月前我還...
    沈念sama閱讀 45,578評(píng)論 2 355
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像睡雇,于是被迫代替她去往敵國(guó)和親萌衬。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,877評(píng)論 2 345

推薦閱讀更多精彩內(nèi)容