清晰賽過聰明: 改進 flatbuffers-go

0. 原由

使用 flatbuffers 已經有至關長的一段時間了.c++

在幾個商用項目中, flatbuffers 也因快速的反序列化而帶來性能上的很多提高.git

flatbuffers 尤爲適合傳輸小塊數據, 一次序列化, 多個地方進行反序列化.github

但 go 的 flatbuffers 有一些小遺憾:golang

  1. go flatbuffers 功能支持, 滯後於 c++ 版, Go 代碼庫也好久沒有更新了. 相比 c ++ , go 版本缺乏一些功能. 如 vector of unions , 在 unions 中包含 struct / strings . ( 注: go 版本的 flatbuffers 在 unions 中只能包含 table )
  2. 缺乏 verifier 驗證器 ( 這是我須要的)
  3. go flatbuffers 序列化的速度, 慢於 gogo protobuf. flatbuffers 序列化消耗的時間, 大約是 gogo protobuf 的兩倍.
  4. go flatbuffers 不支持 go module. 尤爲是自動生成的 go 代碼存在相互引用時的 import 並不友好.
  5. go flatbuffers 的序列化代碼不太優雅, 不太符合 go 的習慣風格

在這樣狀況下, 我起了改進 go flatbuffers 的念頭.api

flatbuffers 的編譯器, 是 c++ 寫的. 我已經不少年沒有用過 c++ 開發了. 對我來講, 這多是一次有趣的探險歷程.bash

1. 我對 go flatbuffers 的折騰

剛開始, 我寫一個 flatbuffers verifier , 本地驗證經過後, 我向 google flatbuffers 發了一個 PR. 結果被建議我重讀一下 flatbuffers 的設計規範文檔. 嗯哼, 這就開始有趣了.架構

在接下來的兩週左右, 我邊讀 flatbuffers 的關鍵規範文檔 ( 見附錄參考列表) , 邊寫了一個全新的序列化生成器 ( flatbuffers builder ) .併發

我拆分了flatbuffers 的 memory block , 採用 goroutine 併發處理各個獨立的 memory block 轉化爲二進制序列數據, 最後進行合併/排序/優化. 當這個手寫序列化器看起來能夠工做時, 我發現, 須要把這些手寫代碼嵌入 flatbuffers 編譯器中, 支持自動代碼生成, 我遇到了一個小難題. 我幾乎忘記如何寫 C++ 了.app

爲此, 我重讀了 Effective C++ 這樣的幾本冊子, 隨書寫幾行代碼跑跑. 一週以後, 從新熟悉 C++ , 意外收穫是對 go 的內存管理有了進一步的認識.post

如何讓 go flatbuffers 序列化更快, 我還在嘗試中.

而熟悉了 C++ 後, 我先讓 go flatbuffers API 變得清晰簡單, 易用一些.

2. 移植 C++ 有用功能, 支持 vector of unions.

union 是 flatbuffers 中頗有趣也頗有用的一個功能, 固然, struct 也頗有用. go flatbuffers 中, union 只支持 table , 而且不支持 union array ( 被稱爲 vector of unions ) , 先加上這個

IDL

union Character {
  MuLan: Attacker,  // table, 至關於 protobuf 中的 message
  Rapunzel,         // struct , 與 c++ 的 struct 至關
  Belle: BookReader,
  BookFan: BookReader,
  Other: string,   // string 
  Unused: string
}

table Movie {
  main_character: Character;      // 單一 union 字段
  characters: [Character];            // vector of unions 
}
複製代碼

3. 支持 go module via Attribute ( 在 IDL 定義中 ).

每個 fbs IDL 定義文件都支持各自的 module , 格式像這樣: "go_module:github.com/tsingson/flatbuffers-sample/go-example/";

weapons.fbs

namespace weapons;

attribute "go_module:github.com/tsingson/flatbuffers-sample/samplesNew/";

table Gun {
  damage:short;
  bool:bool;
  name:string;
  names:[string];
}
複製代碼

monster.fbs

include "../weapons.fbs";

namespace Mygame.Example;

attribute "go_module:github.com/tsingson/flatbuffers-sample/go-example/";

enum Color:byte { Red = 0, Green, Blue = 2 }
union Equipment {   MuLan: Weapon, Weapon, Gun:weapons.Gun, SpaceShip,   Other: string } // Optionally add more tables.

......
複製代碼

生成的 go 代碼

package Example

import (
	"strconv"
	flatbuffers "github.com/google/flatbuffers/go"

	weapons "github.com/tsingson/flatbuffers-sample/samplesNew/weapons"    /// 嗯哼!  
)

type Equipment byte

..........

複製代碼

4. 增長一些清晰易用的 API /生成代碼.

weaponsOffset := flatbuffers.UOffsetT(0)
	if t.Weapons != nil {
		weaponsLength := len(t.Weapons)
		weaponsOffsets := make([]flatbuffers.UOffsetT, weaponsLength)
		for j := weaponsLength - 1; j >= 0; j-- {
			weaponsOffsets[j] = t.Weapons[j].Pack(builder)
		}
		MonsterStartWeaponsVector(builder, weaponsLength)            //////// start
		for j := weaponsLength - 1; j >= 0; j-- {
			builder.PrependUOffsetT(weaponsOffsets[j])
		}
		weaponsOffset = MonsterEndWeaponsVector(builder, weaponsLength)   /////// end 
	}
複製代碼

shortcut for []strings vector

// native object 

	Names []string


// builder

namesOffset := builder.StringsVector( t.Names...)


複製代碼

getter for vector of unions

func (rcv *Movie) Characters(j int, obj *flatbuffers.Table) bool {
	o := flatbuffers.UOffsetT(rcv._tab.Offset(10))
	if o != 0 {
		a := rcv._tab.Vector(o)
		obj.Pos = a + flatbuffers.UOffsetT(j*4)
		obj.Bytes = rcv._tab.Bytes
		return true
	}
	return false
}

複製代碼

so get struct or table

// GetStructVectorAsBookReader shortcut to access struct in vector of unions
func GetStructVectorAsBookReader(table *flatbuffers.Table) *BookReader {
	n := flatbuffers.GetUOffsetT(table.Bytes[table.Pos:])
	x := &BookReader{}
	x.Init(table.Bytes, n+ table.Pos)
	return x
}

// GetStructAsBookReader shortcut to access struct in single union field
func GetStructAsBookReader(table *flatbuffers.Table) *BookReader {
	x := &BookReader{}
	x.Init(table.Bytes, table.Pos)
	return x
}

複製代碼

for object-api , comments in generated code to make it clear

// UnPack use for single union field
 func (rcv Character) UnPack(table flatbuffers.Table) *CharacterT {
	switch rcv {
	case CharacterMuLan:
		x := GetTableAsAttacker(&table)
		return &CharacterT{ Type: CharacterMuLan, Value: x.UnPack() }
 .............

// UnPackVector use for vector of unions 
func (rcv Character) UnPackVector(table flatbuffers.Table) *CharacterT {
	switch rcv {
	case CharacterMuLan:
		x := GetTableVectorAsAttacker(&table)
		return &CharacterT{ Type: CharacterMuLan, Value: x.UnPack() }
	case CharacterRapunzel:
.........
複製代碼

或許, 稍後更多, 讓 Go flatbuffers ...... 更好用.

5. happy hacking....... 折騰繼續中

本文持續有更新...........

.

.


本文首發於 GolangChina , 在此 gocn.vip/topics/1022…


祝安康愉快!

_

_

關於我

網名 tsingson (三明智)

原 ustarcom IPTV/OTT 事業部播控產品線技術架構溼/解決方案工程溼角色(8年), 自由職業者,

喜歡音樂(口琴,是第三/四/五屆廣東國際口琴嘉年華的主策劃人之一), 攝影與越野,

喜歡 golang 語言 (商用項目中主要用 postgres + golang )

tsingson ( 三明智 ) 於深圳南山. 小羅號口琴音樂中心 2020/04/09

相關文章
相關標籤/搜索