開源、高效、跨平臺:深剖Google FlatBuffers工作原理

發表於2015-09-07

JSON——大家可能都知道它是幾乎所有現代伺服器都使用的輕量級資料交換格式。它體量輕,可讀性強,通常比老式的、不友好的XML開發起來更加便捷友好。JSON是不依賴於開發語言的資料格式,但是在解析資料並將其轉換到如Java物件時,會消耗我們的時間和儲存資源。

幾天前,Facebook宣佈,其Android應用程式大幅提升了資料處理效能。這是由於幾乎在全部應用程式中放棄了JSON資料格式,用FlatBuffers取而代之了。閱讀這篇文章可以獲得關於FlatBuffers的基礎知識,學會如何從JSON轉換到FlatBuffers。

雖然這東西是非常有前景的,但是乍一看其實現過程不是一下子就能明白的。而且Facebook也沒有說得很詳細。這就是為什麼我要寫這篇文章,在其中展示我們是如何使用Flatbuffers開展工作的。

FlatBuffers

總之,FlatBuffers是Google專門為遊戲開發而建立的跨平臺序列化庫,就像Facebook所展示的那樣,它在Android平臺上遵循快速響應UI的16ms規則

但是,在把所有資料遷移到FlatBuffers之前,你要確定確實需要這樣做。因為,這樣做有時對效能的影響是潛移默化的,而且資料安全性要比計算速度上幾十毫秒的差異更重要。

什麼使得Flatbuffers如此奏效?

  • 由於是以二進位制形式快取,訪問序列化資料時也無需資料解析過程。即使對於層次化資料也不需要解析。多虧不需要初始化解析器(初始化意味著要建立複雜的欄位對映)和解析資料,這些都是需要花費時間的。
  • Flatbuffers資料不需要分配比自身使用緩衝區還要多的記憶體。我們不必像在JSON中那樣為解析資料的整個層次分配額外物件。

要獲得正宗的資料,就再讀一讀Facebook上關於FlatBuffers遷移問題的文章,還有Google自己的文件。

實現

本文將介紹在Android應用程式中使用Flatbuffers的最簡單方法:

  • JSON資料在應用程式之外的某個地方被轉換成FlatBuffer格式的檔案(例如,將二進位制資料以檔案的形式提交,還可以從API直接返回FlatBuffer二進位制檔案)。
  • 在flatc (FlatBuffer編譯器)的幫助下,手工生成資料模型(Java類)。
  • JSON檔案存在一定的侷限性(不能使用null欄位,日期格式也被解析為字串)。

將來,我們或許會提出更復雜的解決方案。

FlatBuffers編譯器

首先,我們需要flatc,即flatbuffers編譯器。該編譯器可以從Google所屬的原始碼構建,原始碼位於Flatbuffers資源庫中。我們下載並克隆它。整個構建過程在FlatBuffers構建文件中都做了描述。如果你是Mac使用者的話,需要這樣來構建:

1.在\{extract directory}\build\XcodeFlatBuffers.xcodeproj路徑下,開啟已下載的原始碼。
2.點選Play按鈕或⌘ + R,執行flatc scheme(預設情況下應該是被選中的)。
3.flatc可執行檔案就會在專案的根目錄下出現。
現在,我們可以使用schema編譯器了,該編譯器能夠把給定的schema(在Java、C#、Python、GO和C++語言中的schema)生成為模型類,還可以把JSON轉換成Flatbuffer的二進位制檔案。

Schema檔案

接著,我們必須準備schema檔案,該檔案定義了要進行序列化和反序列化的資料結構。這個schema將用於flatc建立Java模型,把JSON轉換成FlatBuffers的二進位制檔案。

這裡是JSON檔案的一部分。

完整版本在這裡。這是略微修改後的版本,可以從Github API呼叫:https://api.github.com/users/google/repos

Flatbuffer schema是編寫得很好的文件,所以就不深入探討這個問題了。另外,本文中的schema不會很複雜。我們所要做的僅僅是建立3張表:ReposList, Repo和User,並定義root_type。這是schema的重要組成部分。

完整的schema檔案在這裡

FlatBuffers資料檔案

真棒,我們現在要做的是把repos_json.json轉換成FlatBuffers二進位制檔案,生成能夠以Java風格表示資料的Java模型(此處操作所需的全部檔案都在我們的程式碼庫中):

Java程式碼

如果一切順利,會產生下列檔案:

  • repos_json.bin(要將重新命名它為 repos_flat.bin)
  • Repos/Repo.java
  • Repos/ReposList.java
  • Repos/User.java

Android應用程式
現在來建立示例程式,在實踐中來看看Flatbuffers格式是如何起作用的。這是截圖:

0ed4bf81-5360-3b81-9dc3-82142439b068

在UI部分,ProgressBar僅用於顯示不恰當的資料處理對使用者介面順暢度的影響。

應用檔案看起來是這個樣子:app/build.gradle

當然,在本例中不是必須要用Rx或ButterKnife這樣的檢視注入利器,但是為什麼不讓應用更細緻一些呢??

我們把repos_flat.bin 和 repos_json.json檔案放到res/raw/目錄下。RawDataReader是工具類,它幫助我們讀取Android應用中的原始檔案。

最後,把Repo,ReposList和User這三張表對應的模型類程式碼放到專案原始碼中。

FlatBuffers庫

使用Java語言程式設計過程中,FlatBuffers提供了可以直接處理這種資料格式的庫,也這是flatbuffers-java-1.2.0-SNAPSHOT.jar檔案。如果你想手工生成該檔案,需要下載FlatBuffers原始碼,再到目錄java/下,用Maven生成該庫:

Java程式碼

現在將.jar檔案放到Android專案的app/libs/目錄下。

好了,當務之急是實現MainActivity類,這是完整原始碼。

我們最為關注的兩個方法是:

  • parseReposListJson(String reposStr) – 這個方法初始化Gson解析器,並把JSON字串轉換成Java物件。
  • loadFlatBuffer(byte[] bytes)  – 這個方法將位元組(repos_flat.bin檔案)轉換成Java物件。

使用FlatBuffers的結果

現在讓我們把JSON和FlatBuffers在載入時間和資源消耗方面的差異形象化。測試是在帶有Android M(beta版)的Nexus 5上進行的。

使用FlatBuffers的結果

現在讓我們把JSON和FlatBuffers在載入時間和資源消耗方面的差異形象化。測試是在帶有Android M(beta版)的Nexus 5上進行的。

載入時間

測量的過程是將其他檔案轉換為Java原始檔,對所有(90個)元素進行迭代。

  • 使用JSON:JSON檔案(大小:478kB)平均載入時間200ms(時間區間:180ms~250ms);
  • 使用FlatBuffers:FlatBuffers二進位制檔案(大小:352kB)平均載入時間5ms(時間區間:3ms~10ms)。

記得16ms規則嗎?我們在UI執行緒中呼叫這些方法的原因就是要看看在這種情況下介面表現如何:

JSON資料載入效果:

dd112187-4554-34d1-b874-eace412cc32e

FlatBuffers資料載入效果:

看出區別了嗎?JSON資料的載入過程中, ProgressBar停頓了一會,介面不是那麼順暢(載入時間超過了16ms)。

記憶體分配、CPU等資源

還有什麼想要測量的嗎?也許應該測量一下Android Studio 1.3,還有那些新特性。例如,記憶體分配跟蹤器(Allocation Tracker),記憶體狀態檢視器(Memory Viewer)和方法跟蹤器(Method Tracer)。

原始碼

這裡所講解專案的完整原始碼都在Github程式碼庫中。你不需要接觸整個FlatBuffers專案,所需的內容全都在flatbuffers/目錄下。(翻譯/張揮戈  友情審校/白雲鵬)

文章來源:froger_mcs dev blog

作者簡介:

Miroslaw Stanek,Azimo Money Transfer公司移動專案負責人,Android和iOS平臺程式設計師,視訊遊戲玩家,冰雪運動愛好者。個人部落格:http://frogermcs.github.io。

譯者簡介:

張揮戈,長期從事計算機軟體開發、專案管理和產品設計工作,曾在多家移動網際網路公司任技術總監。關注Android平臺相關技術以及其他平臺客戶端軟體開發相關話題。

相關文章