作者 | 楊朝坤(慕白) 策劃 | 鄧艷琴
Fury 是一個(gè)基于 JIT 動(dòng)態(tài)編譯和零拷貝的多語(yǔ)言序列化框架,支持 Java/Python/Golang/JavaScript/C++ 等語(yǔ)言,提供全自動(dòng)的對(duì)象多語(yǔ)言 / 跨語(yǔ)言序列化能力,和相比 JDK 最高 170 倍的性能。
代碼主倉(cāng)庫(kù)的 GitHub 地址為:https://github.com/alipay/fury
背景
序列化是系統(tǒng)通信的基礎(chǔ)組件,在大數(shù)據(jù)、AI 框架和云原生等分布式系統(tǒng)中廣泛使用。當(dāng)對(duì)象需要跨進(jìn)程、跨語(yǔ)言、跨節(jié)點(diǎn)傳輸、持久化、狀態(tài)讀寫(xiě)、復(fù)制時(shí),都需要進(jìn)行序列化,其性能和易用性影響運(yùn)行效率和開(kāi)發(fā)效率。
靜態(tài)序列化框架 protobuf/flatbuffer/thrift 由于不支持對(duì)象引用和多態(tài)、需要提前生成代碼等原因,無(wú)法作為領(lǐng)域?qū)ο笾苯用嫦驊?yīng)用進(jìn)行跨語(yǔ)言開(kāi)發(fā)。而動(dòng)態(tài)序列化框架 JDK 序列化 /Kryo/Fst/Hessian/Pickle 等,盡管提供了易用性和動(dòng)態(tài)性,但不支持跨語(yǔ)言,且性能存在顯著不足,并不能滿足高吞吐、低延遲和大規(guī)模數(shù)據(jù)傳輸場(chǎng)景需求。
因此,我們開(kāi)發(fā)了一個(gè)新的多語(yǔ)言序列化框架 Fury,并正式在 Github 開(kāi)源。通過(guò)一套高度優(yōu)化的序列化基礎(chǔ)原語(yǔ),結(jié)合JIT 動(dòng)態(tài)編譯和 Zero-Copy等技術(shù),同時(shí)滿足了性能、功能和易用性的需求,實(shí)現(xiàn)了任意對(duì)象自動(dòng)跨語(yǔ)言序列化,并提供極致的性能。
Fury 簡(jiǎn)介
Fury 是一個(gè)基于 JIT 動(dòng)態(tài)編譯和零拷貝的多語(yǔ)言序列化框架,提供極致的性能和易用性:
支持主流編程語(yǔ)言Java/ Python/ C++/ Golang/ JavaScript,其它語(yǔ)言可輕易擴(kuò)展;
統(tǒng)一的多語(yǔ)言序列化核心能力:
高度優(yōu)化的序列化原語(yǔ);
Zero-Copy 序列化支持,支持 Out of band 序列化協(xié)議,支持堆外內(nèi)存讀寫(xiě);
基于JIT 動(dòng)態(tài)編技術(shù)在運(yùn)行時(shí)異步多線程自動(dòng)生成序列化代碼優(yōu)化性能,增加方法內(nèi)聯(lián)、代碼緩存和消除死代碼,減少虛方法調(diào)用 / 條件分支 /Hash 查找 / 元數(shù)據(jù)寫(xiě)入 / 內(nèi)存讀寫(xiě)等,提供相比別的序列化框架最高 170 倍的性能;
多協(xié)議支持:兼顧動(dòng)態(tài)序列化的靈活性和易用性,以及靜態(tài)序列化的跨語(yǔ)言能力。
Java 序列化:
無(wú)縫替代 JDK/Kryo/Hessian,無(wú)需修改任何代碼,但提供最高 170x 的性能,可以大幅提升高性能場(chǎng)景RPC 調(diào)用、數(shù)據(jù)傳輸和對(duì)象持久化效率;
100% 兼容 JDK 序列化,原生支持 JDK 自定義序列化方法 writeObject/ readObject/ writeReplace/ readResolve/ readObjectNoData
跨語(yǔ)言對(duì)象圖序列化:
多語(yǔ)言 / 跨語(yǔ)言自動(dòng)序列化任意對(duì)象,無(wú)需創(chuàng)建 IDL 文件、手動(dòng)編譯 schema 生成代碼以及將對(duì)象轉(zhuǎn)換為中間格式;
多語(yǔ)言 / 跨語(yǔ)言自動(dòng)序列化共享引用和循環(huán)引用,不需要關(guān)心數(shù)據(jù)重復(fù)或者遞歸錯(cuò)誤;
支持對(duì)象類(lèi)型多態(tài),多個(gè)子類(lèi)型對(duì)象可以同時(shí)被序列化;
行存序列化:
提供緩存友好的二進(jìn)制隨機(jī)訪問(wèn)行存格式,支持跳過(guò)序列化和部分序列化,適合高性能計(jì)算和大規(guī)模數(shù)據(jù)傳輸場(chǎng)景;
支持和 Arrow 列存自動(dòng)互轉(zhuǎn) ;
序列化核心能力
盡管不同的場(chǎng)景對(duì)序列化有需求,但序列化的底層操作都是類(lèi)似的。因此 Fury 定義和實(shí)現(xiàn)了一套序列化的基礎(chǔ)能力,基于這套能力能夠快速構(gòu)建不同的多語(yǔ)言序列化協(xié)議,并通過(guò)編譯加速等優(yōu)化具備高性能。同時(shí)針對(duì)一種協(xié)議在基礎(chǔ)能力上的性能優(yōu)化,也能夠讓所有的序列化協(xié)議都受益。
序列化原語(yǔ)
序列化涉及的常見(jiàn)操作主要包括:
bitmap 位操作
整數(shù)編解碼
整數(shù)壓縮
字符串創(chuàng)建 * 拷貝優(yōu)化
字符串編碼:ASCII/UTF8/UTF16
內(nèi)存拷貝優(yōu)化
數(shù)組拷貝壓縮優(yōu)化
元數(shù)據(jù)編碼 & 壓縮 & 緩存
Fury 針對(duì)這些操作在每種語(yǔ)言?xún)?nèi)部都做了大量的優(yōu)化,結(jié)合 SIMD 指令和語(yǔ)言高級(jí)特性,將性能推到極致,從而方便不同協(xié)議使用。
零拷貝序列化
在大規(guī)模數(shù)據(jù)傳輸場(chǎng)景,一個(gè)對(duì)象圖內(nèi)部往往有多個(gè) binary buffer,而序列化框架在序列化過(guò)程當(dāng)中會(huì)把這些數(shù)據(jù)寫(xiě)入一個(gè)中間 buffer,引入多次耗時(shí)內(nèi)存拷貝。Fury 借鑒了 pickle5、ray 以及 arrow 的零拷貝設(shè)計(jì),實(shí)現(xiàn)了一套Out-Of-Band 序列化協(xié)議,能夠把一個(gè)對(duì)象圖當(dāng)中的所有 binary buffer 直接抓取出來(lái),避免掉這些 buffer 的中間拷貝,將序列化期間的內(nèi)存拷貝開(kāi)銷(xiāo)降低到 0。
下圖是 Fury 關(guān)閉引用支持時(shí) Zero-Copy 的大致序列化過(guò)程。
目前 Fury 內(nèi)置了以下類(lèi)型的 Zero-Copy 支持:
Java:所有基本類(lèi)型數(shù)組、ByteBuffer、ArrowRecordBatch、VectorSchemaRoot
Python:array 模塊的所有 array、numpy 數(shù)組、pyarrow.Table、pyarrow.RecordBatch
Golang:byte slice
用戶也可以基于 Fury 的接口擴(kuò)展新的零拷貝類(lèi)型。
JIT 動(dòng)態(tài)編譯加速
對(duì)于要序列化的自定義類(lèi)型對(duì)象,其中通常包含大量類(lèi)型信息,F(xiàn)ury利用這些類(lèi)型信息在運(yùn)行時(shí)直接生成高效的序列化代碼,將大量運(yùn)行時(shí)的操作在動(dòng)態(tài)編譯階段完成,從而增加方法內(nèi)聯(lián)和代碼緩存,減少虛方法調(diào)用 / 條件分支 /Hash 查找 / 元數(shù)據(jù)寫(xiě)入 / 內(nèi)存讀寫(xiě)等,最終大幅加速了序列化性能。
對(duì)于 Java 語(yǔ)言,F(xiàn)ury 實(shí)現(xiàn)了一套運(yùn)行時(shí)代碼生成框架,定義了一套序列化邏輯的算子表達(dá)式 IR,在運(yùn)行時(shí)基于對(duì)象類(lèi)型的泛型信息進(jìn)行類(lèi)型推斷,然后構(gòu)建一顆描述序列化代碼邏輯的表達(dá)式樹(shù),根據(jù)表達(dá)式樹(shù)生成高效的 Java 代碼,再在運(yùn)行時(shí)通過(guò) Janino 編譯成字節(jié)碼,再加載到用戶的 ClassLoader 里面或者 Fury 創(chuàng)建的 ClassLoader 里面,最終通過(guò) Java JIT 編譯成高效的匯編代碼。
由于 JVM JIT 會(huì)跳過(guò)大方法編譯和內(nèi)聯(lián),F(xiàn)ury 也實(shí)現(xiàn)了一套優(yōu)化器,將大方法遞歸拆分成小方法,這樣就保證了 Fury 生成的所有代碼都可以被編譯和內(nèi)聯(lián),壓榨 JVM 的性能到極致。
同時(shí) Fury 也支持異步多線程動(dòng)態(tài)編譯,將不同序列化器的代碼生成任務(wù)提交到線程池執(zhí)行,在編譯完成之前使用解釋模式執(zhí)行,從而保證不會(huì)出現(xiàn)序列化毛刺,不需要提前預(yù)熱所有類(lèi)型的序列化。
Python 和 JavaScript 場(chǎng)景也是采用的類(lèi)似代碼生成方式,這樣的生成方式開(kāi)發(fā)門(mén)檻低,更容易排查問(wèn)題。
由于序列化需要密切操作每種編程語(yǔ)言的對(duì)象,而編程語(yǔ)言并沒(méi)有暴露內(nèi)存模型的低階 API,通過(guò) Native 方法調(diào)用存在較大開(kāi)銷(xiāo),因此我們并不能通過(guò) LLVM 構(gòu)建一個(gè)統(tǒng)一的序列化器 JIT 框架,而是需要在每種語(yǔ)言?xún)?nèi)部結(jié)合語(yǔ)言特性實(shí)現(xiàn)特定的代碼生成框架以及序列化器構(gòu)建邏輯。
靜態(tài)代碼生成
盡管 JIT 編譯能夠大幅提升序列化效率,并且在運(yùn)行時(shí)能夠根據(jù)數(shù)據(jù)的統(tǒng)計(jì)分布重新生成更優(yōu)的序列化代碼,但 C++/Rust 等語(yǔ)言不支持反射,沒(méi)有虛擬機(jī),也沒(méi)有提供內(nèi)存模型的低階 API,因此我們無(wú)法針對(duì)這類(lèi)語(yǔ)言通過(guò) JIT 動(dòng)態(tài)編譯生成序列化代碼。
對(duì)于此類(lèi)場(chǎng)景,F(xiàn)ury 正在實(shí)現(xiàn)一套 AOT 靜態(tài)代碼生成框架,在編譯時(shí)根據(jù)對(duì)象的 schema 提前生成序列化代碼,然后使用生成的代碼進(jìn)行自動(dòng)序列化。對(duì)于 Rust,未來(lái)也會(huì)通過(guò) Rust 的 macro 在編譯時(shí)生成代碼,提供更好的易用性。
緩存優(yōu)化
在序列化自定義類(lèi)型時(shí),會(huì)把字段進(jìn)行重排序,保證相同接口類(lèi)型的字段依次序列化,增加緩存命中的概率,同時(shí)也促進(jìn)了CPU 指令緩存,實(shí)現(xiàn)了更加高效的序列化。對(duì)于基本類(lèi)型字段將寫(xiě)入順序按照字節(jié)字段大小降序排列,這樣如果開(kāi)始地址是對(duì)齊的,隨后的讀寫(xiě)都會(huì)發(fā)生在內(nèi)存地址對(duì)齊的位置,CPU 執(zhí)行起來(lái)更加高效。
多協(xié)議設(shè)計(jì)與實(shí)現(xiàn)
基于 Fury 提供的多語(yǔ)言序列化核心能力,我們?cè)谶@之上構(gòu)建了三種序列化協(xié)議,分別適用于不同的場(chǎng)景:
Java 序列化:適合純 Java 序列化場(chǎng)景,提供最高百倍以上的性能提升;
跨語(yǔ)言對(duì)象圖序列化:適合面向應(yīng)用的多語(yǔ)言編程,以及高性能跨語(yǔ)言序列化;
行存序列化:適合分布式計(jì)算引擎如 Spark/Flink/Dories/Velox/ 樣本流處理框架 / 特征存儲(chǔ)等;
后續(xù)我們也會(huì)針對(duì)一些核心場(chǎng)景添加新的協(xié)議,用戶也可以基于 Fury 的序列化能力構(gòu)建自己的協(xié)議。
Java 序列化
由于 Java 在大數(shù)據(jù)、云原生、微服務(wù)和企業(yè)級(jí)應(yīng)用的廣泛使用,對(duì) Java 序列化的性能優(yōu)化可以大幅降低系統(tǒng)延遲,提升吞吐率,降低服務(wù)器成本。
因此 Fury 針對(duì) Java 序列化進(jìn)行了大量極致性能優(yōu)化,我們的實(shí)現(xiàn)具備以下能力:
極致性能:通過(guò)利用 Java 對(duì)象的類(lèi)型和泛型信息,結(jié)合 JIT 編譯、Unsafe 低階操作,F(xiàn)ury 相比 JDK 最高有 170 倍的性能提升,相比 Kryo/Hessian 最高有 50~100 倍的性能提升。
100% JDK 序列化 API 兼容性:支持了所有 JDK 自定義序列化方法 writeObject/readObject/ writeReplace/ readResolve/readObjectNoData 的語(yǔ)義,保證任意場(chǎng)景替換 JDK 序列化的正確性。而已有的 Java 序列化框架如 Kryo/Hessian 在這些場(chǎng)景,都存在一定的正確性問(wèn)題
類(lèi)型前后兼容:在反序列化端和序列化端 Class Schema 不一致時(shí),仍然可以正確反序列化,支持應(yīng)用獨(dú)立升級(jí)部署,獨(dú)立增刪字段。并且我們對(duì)元數(shù)據(jù)進(jìn)行了極致的壓縮和共享,類(lèi)型兼容模式相比類(lèi)型強(qiáng)一致模式做到了幾乎沒(méi)有任何性能損失。
元數(shù)據(jù)共享:在某個(gè)上下文 (TCP 連接) 下多次序列化之間共享元數(shù)據(jù)(類(lèi)名稱(chēng)、字段名稱(chēng)、Final 字段類(lèi)型信息等),這些信息會(huì)在該上下文下第一次序列化時(shí)發(fā)送到對(duì)端,對(duì)端可以根據(jù)該類(lèi)型信息重建相同的反序列化器,后續(xù)序列化可以避免傳輸元數(shù)據(jù),減小網(wǎng)絡(luò)流量壓力,同時(shí)也自動(dòng)支持類(lèi)型前后兼容。
零拷貝支持:支持 Out of band 零拷貝和堆外內(nèi)存讀寫(xiě)。
跨語(yǔ)言對(duì)象圖序列化
跨語(yǔ)言對(duì)象圖序列化主要用于對(duì)動(dòng)態(tài)性和易用性有更高要求的場(chǎng)景。盡管 Protobuf/Flatbuffer 等框架提供了多語(yǔ)言序列化能力,但仍然存在一些不足:
需要提前編寫(xiě) IDL 并靜態(tài)編譯生成代碼,不具備足夠的動(dòng)態(tài)性和靈活性;
生成的類(lèi)不符合面向?qū)ο笤O(shè)計(jì)也無(wú)法給類(lèi)添加行為,并不能作為領(lǐng)域?qū)ο笾苯佑糜诙嗾Z(yǔ)言應(yīng)用開(kāi)發(fā)。
不支持子類(lèi)序列化。面向?qū)ο缶幊痰闹饕攸c(diǎn)是通過(guò)接口調(diào)用子類(lèi)方法。這類(lèi)模式也無(wú)法得到很好的支持。盡管 Flatbuffer 提供了 Union,Protobuf 提供了 OneOf/Any 特性,這類(lèi)特性需要在序列化和反序列化時(shí)判斷對(duì)象的類(lèi)型,不符合面向?qū)ο缶幊痰脑O(shè)計(jì)。
不支持循環(huán)和共享引用,需要針對(duì)領(lǐng)域?qū)ο笾匦露x一套 IDL 并自己實(shí)現(xiàn)引用解析,然后在每種語(yǔ)言里面編寫(xiě)代碼實(shí)現(xiàn)領(lǐng)域?qū)ο蠛蛥f(xié)議對(duì)象之間的相互轉(zhuǎn)換,如果對(duì)象圖嵌套層數(shù)較深,則需要編寫(xiě)更多的代碼。
結(jié)合以上幾點(diǎn),F(xiàn)ury 實(shí)現(xiàn)了一套跨語(yǔ)言的對(duì)象圖序列化協(xié)議:
多語(yǔ)言 / 跨語(yǔ)言自動(dòng)序列化任意對(duì)象:在序列化和反序列化端定義兩個(gè) Class,即可自動(dòng)將一種語(yǔ)言的對(duì)象自動(dòng)序列化為另一種語(yǔ)言的對(duì)象,無(wú)需創(chuàng)建 IDL 文件、編譯 schema 生成代碼以及手寫(xiě)轉(zhuǎn)換代碼;
多語(yǔ)言 / 跨語(yǔ)言自動(dòng)序列化共享引用和循環(huán)引用;
支持對(duì)象類(lèi)型多態(tài),符合面向?qū)ο缶幊谭妒剑鄠€(gè)子類(lèi)型對(duì)象可以同時(shí)被自動(dòng)反序列化,無(wú)需用戶手動(dòng)處理;
同時(shí)我們?cè)谶@套協(xié)議上面也支持了 Out of band 零拷貝;
自動(dòng)跨語(yǔ)言序列化示例:
行存序列化
對(duì)于高性能計(jì)算和大規(guī)模數(shù)據(jù)傳輸場(chǎng)景,數(shù)據(jù)序列化和傳輸往往是整個(gè)系統(tǒng)的性能瓶頸。如果用戶只需要讀取部分?jǐn)?shù)據(jù),或者根據(jù)對(duì)象某個(gè)字段進(jìn)行過(guò)濾,反序列化整個(gè)數(shù)據(jù)將帶來(lái)額外開(kāi)銷(xiāo)。因此 Fury 也提供了一套二進(jìn)制數(shù)據(jù)結(jié)構(gòu),在二進(jìn)制數(shù)據(jù)上直讀直寫(xiě),避開(kāi)序列化。
Apache arrow 是一個(gè)成熟的列存格式,支持二進(jìn)制讀寫(xiě)。但列存并不能滿足所有場(chǎng)景需求,在線鏈路和流式計(jì)算場(chǎng)景的數(shù)據(jù)天然就是行存結(jié)構(gòu),同時(shí)列式計(jì)算引擎內(nèi)部在涉及到數(shù)據(jù)變更和 Hash/Join/Aggregation 操作時(shí),也會(huì)使用到行存結(jié)構(gòu)。
而行存并沒(méi)有一個(gè)統(tǒng)一標(biāo)準(zhǔn)實(shí)現(xiàn),計(jì)算引擎如 Spark/Flink/Doris/Velox 等都定義了一套行存格式,這些格式不支持跨語(yǔ)言,且只能被自己引擎內(nèi)部使用,無(wú)法用于其它框架。盡管 Flatbuffer 能夠支持按需反序列化,但需要靜態(tài)編譯 Schema IDL 和管理 offset,無(wú)法滿足復(fù)雜場(chǎng)景的動(dòng)態(tài)性和易用性需求。
因此 Fury 在早期借鑒了 spark tungsten 和 apache arrow 格式,實(shí)現(xiàn)了一套可以隨機(jī)訪問(wèn)的二進(jìn)制行存結(jié)構(gòu),目前實(shí)現(xiàn)了 Java/Python/C++ 版本,實(shí)現(xiàn)了在二進(jìn)制數(shù)據(jù)上面直讀直寫(xiě),避免掉了所有序列化開(kāi)銷(xiāo)。
下圖是 Fury Row Format 的二進(jìn)制格式:
該格式密集存儲(chǔ),數(shù)據(jù)對(duì)齊,緩存友好,讀寫(xiě)更快。由于避免了反序列化,能夠減少 Java GC 壓力。同時(shí)降低 Python 開(kāi)銷(xiāo),同時(shí)由于 Python 的動(dòng)態(tài)性,F(xiàn)ury 的數(shù)據(jù)結(jié)構(gòu)實(shí)現(xiàn)了 _getattr__/getitem/slice/ 和其它特殊方法,保證了行為跟 python dataclass/list/object 的一致性,用戶沒(méi)有任何感知。
性能對(duì)比
這里給出部分 Java 序列化性能數(shù)據(jù),其中標(biāo)題包含 compatible 的圖表是支持類(lèi)型前后兼容下的性能數(shù)據(jù),標(biāo)題不包含 compatible 的圖表是不支持類(lèi)型前后兼容下的性能數(shù)據(jù)。為了公平起見(jiàn),所有測(cè)試 Fury 關(guān)閉了零拷貝特性。
更多 benchmark 數(shù)據(jù)請(qǐng)參考 Fury Github 官方文檔
未來(lái)規(guī)劃
元數(shù)據(jù)壓縮和自動(dòng)共享
跨語(yǔ)言序列化支持類(lèi)型前后兼容
靜態(tài)代碼生成框架,用于提前生成 c++/golang/rust 代碼
C++/Rust 支持跨語(yǔ)言對(duì)象圖序列化
Golang/Rust/JavaScript 支持行存
兼容 ProtoBuffer 生態(tài),支持根據(jù) Proto IDL 自動(dòng)生成 Fury 序列化代碼
新的協(xié)議實(shí)現(xiàn):AI 特征存儲(chǔ),知識(shí)圖譜序列化
持續(xù)改進(jìn)我們的序列化基礎(chǔ)原語(yǔ),提供更高性能實(shí)現(xiàn)
標(biāo)準(zhǔn)化協(xié)議,提供二進(jìn)制兼容性
文檔和易用性改進(jìn)
加入我們
我們致力于將 Fury 打造為一個(gè)開(kāi)放中立、追求極致與創(chuàng)新的社區(qū)項(xiàng)目,后續(xù)的研發(fā)與討論等工作都會(huì)在社區(qū)以開(kāi)源透明的方式進(jìn)行。歡迎任何形式的參與,包括但不限于提問(wèn)、代碼貢獻(xiàn)、技術(shù)討論等。非常期待收到大家的想法和反饋,一起參與到項(xiàng)目的建設(shè)中來(lái),推動(dòng)項(xiàng)目向前發(fā)展,打造最先進(jìn)的序列化框架。
代碼主倉(cāng)庫(kù)的 GitHub 地址為:https://github.com/alipay/fury
作者簡(jiǎn)介
楊朝坤,螞蟻集團(tuán)技術(shù)專(zhuān)家,F(xiàn)ury 框架作者。2018 年加入螞蟻集團(tuán),先后從事流計(jì)算框架、在線學(xué)習(xí)框架、科學(xué)計(jì)算框架和 Ray 等分布式計(jì)算框架開(kāi)發(fā),對(duì)批計(jì)算、流計(jì)算、Tensor 計(jì)算、高性能計(jì)算、AI 框架、張量編譯等有深入的理解。
審核編輯:湯梓紅
-
開(kāi)源
+關(guān)注
關(guān)注
3文章
3245瀏覽量
42396 -
C++
+關(guān)注
關(guān)注
22文章
2104瀏覽量
73487 -
JDK
+關(guān)注
關(guān)注
0文章
81瀏覽量
16576 -
Rust
+關(guān)注
關(guān)注
1文章
228瀏覽量
6570 -
螞蟻集團(tuán)
+關(guān)注
關(guān)注
0文章
92瀏覽量
3573
原文標(biāo)題:比 JDK 最高快 170 倍,螞蟻集團(tuán)開(kāi)源高性能多語(yǔ)言序列化框架 Fury
文章出處:【微信號(hào):AI前線,微信公眾號(hào):AI前線】歡迎添加關(guān)注!文章轉(zhuǎn)載請(qǐng)注明出處。
發(fā)布評(píng)論請(qǐng)先 登錄
相關(guān)推薦
評(píng)論