清晰勝過聰明: 改進 flatbuffers-go[更新記憶體洩露與 GC]
0. 起因
使用 flatbuffers 已經有相當長的一段時間了.
在幾個商用專案中, flatbuffers 也因快速的反序列化而帶來效能上的不少提升.
flatbuffers 尤其適合傳輸小塊資料, 一次序列化, 多個地方進行反序列化.
但 go 的 flatbuffers 有一些小遺憾:
- go flatbuffers 功能支援, 滯後於 c++ 版, Go 程式碼庫也很久沒有更新了. 相比 c ++ , go 版本缺少一些功能. 如 vector of unions , 在 unions 中包含 struct / strings . ( 注: go 版本的 flatbuffers 在 unions 中只能包含 table )
- 缺少 verifier 驗證器 ( 這是我需要的)
- go flatbuffers 序列化的速度, 慢於 gogo protobuf. flatbuffers 序列化消耗的時間, 大約是 gogo protobuf 的兩倍.
- go flatbuffers 不支援 go module. 尤其是自動生成的 go 程式碼存在相互引用時的 import 並不友好.
- go flatbuffers 的序列化程式碼不太優雅, 不太符合 go 的習慣風格
在這樣情況下, 我起了改進 go flatbuffers 的念頭.
flatbuffers 的編譯器, 是 c++ 寫的. 我已經很多年沒有用過 c++ 開發了. 對我來說, 這可能是一次有趣的探險歷程.
1. 我對 go flatbuffers 的折騰
剛開始, 我寫一個 flatbuffers verifier , 本地驗證通過後, 我向 google flatbuffers 發了一個 PR. 結果被建議我重讀一下 flatbuffers 的設計規範文件. 嗯哼, 這就開始有趣了.
在接下來的兩週左右, 我邊讀 flatbuffers 的關鍵規範文件 ( 見附錄參考列表) , 邊寫了一個全新的序列化生成器 ( flatbuffers builder ) .
我拆分了 flatbuffers 的 memory block , 採用 goroutine 併發處理各個獨立的 memory block 轉化為二進位制序列資料, 最後進行合併/排序/優化. 當這個手寫序列化器看起來可以工作時, 我發現, 需要把這些手寫程式碼嵌入 flatbuffers 編譯器中, 支援自動程式碼生成, 我遇到了一個小難題. 我幾乎忘記如何寫 C++ 了.
為此, 我重讀了 Effective C++ 這樣的幾本冊子, 隨書寫幾行程式碼跑跑. 一週之後, 重新熟悉 C++ , 意外收穫是對 go 的記憶體管理有了進一步的認識.
如何讓 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. 關於記憶體洩露與 Go GC
C++ 程式碼在 CI 時提示記憶體洩露, 查了一整天..........
看 C++ 程式碼
// Save out the generated code for a Go Table type.
bool SaveType(const Definition &def, const std::string *classcode,
const bool needs_imports, const bool is_enum) {
if (!classcode->length()) return true;
// fix miss name space issue
auto dns= new Namespace();
if ((parser_.root_struct_def_) &&
(def.defined_namespace->components.empty())) {
dns->components.push_back(parser_.root_struct_def_->name);
} else {
dns = def.defined_namespace;
}
Namespace &ns = go_namespace_.components.empty() ? *dns : go_namespace_;
auto dns= new Namespace(); -----------> 定義了一個指標變數, 並且初始化 在下面的 if 語句中使用了該指標變數, 但在 if else 程式碼塊中, dns 指標變數被指向另一個 Namespace 指標, 這樣在 if 語句中的指標變數成了野指標, 造成記憶體洩露
修改後程式碼如下, 注: 把指標使用程式碼移動到 if else 程式碼塊中, 在哪裡定義指標在哪裡使用
// Save out the generated code for a Go Table type.
bool SaveType(const Definition &def, const std::string *classcode,
const bool needs_imports, const bool is_enum) {
if (!classcode->length()) return true;
// fix miss name space issue
if ((parser_.root_struct_def_) &&
(def.defined_namespace->components.empty())) {
auto dns = new Namespace();
dns->components.push_back(parser_.root_struct_def_->name);
Namespace &ns = go_namespace_.components.empty() ? *dns : go_namespace_;
..........
} else {
Namespace &ns = go_namespace_.components.empty() ? *def.defined_namespace : go_namespace_;
................
}
在 go 中, 如果使用同樣的代友, 例如
type Namespace struct {
Components Stack; // 這是一個FILO 的 stack 堆結構, 支援 Push / Pop 以及在 Pushback 及 Popback 在 stack 尾部新增元素或彈出元素
...
}
func SaveType ( def Definition, classcode *string , needs_imports, is _enum bool ) bool {
.......
dns = new Namespace;
if ( ................... ) {
dns. Components.Pushback( .........)
} else {
dns = def.DefinedNamespace; // 這是一個已經存在的 Namespace 指標
}
這樣的寫法, 其實與 C++ 一樣, 原來 new 產生的 dns 會有記憶體洩露的可能, 但這個 dns 在 Go 中由於已經沒有任何引用, 所以, 稍後被 go runtime 執行時進行 GC 了.
go 果然是 big C , 增強的 C++, GC 的存在讓開發過程輕鬆很多.
不過, 指標型別變數的引用, 可能引起記憶體洩露, 以及哪些情況可能被 GC , 哪些情況不會被 GC , 開發時得有個心數. 比如使用 go unsafe 時要和 C++ 一樣小心.
6. happy hacking....... 折騰繼續中
本文持續有更新...........
- 程式碼在 http://github.com/tsingson/flatbuffers
- 示例在 http://github.com/tsingson/flatbuffers-sample
- PR 在 https://github.com/google/flatbuffers/pull/5851
如果你對 flatbuffers 的 Go 版本有任何想法, 請簡單直接告訴我 ( 發郵件到 tsingson_at_me_com, 看看我能做些什麼有趣的...... 謝先.
祝安康愉快!
_
本站首發, 轉載請使用本頁連線網址, 謝
_
關於我
網名 tsingson (三明智)
原 ustarcom IPTV/OTT 事業部播控產品線技術架構溼/解決方案工程溼角色 (8 年), 自由職業者,
喜歡音樂 (口琴,是第三/四/五屆廣東國際口琴嘉年華的主策劃人之一), 攝影與越野,
喜歡 golang 語言 (商用專案中主要用 postgres + golang )
_
_
_ tsingson ( 三明智 ) 於深圳南山. 小羅號口琴音樂中心 2020/04/09
_
_
_
- 加微信實戰群請加微信(註明:實戰群):gocnio
相關文章
- 記憶體洩露記憶體洩露
- 使用 GC、Objgraph 幹掉 Python 記憶體洩露與迴圈引用!GCOBJPython記憶體洩露
- MFC記憶體洩露與檢測記憶體洩露
- JVM與記憶體洩露問題JVM記憶體洩露
- Java 記憶體洩露的理解與解決過程Java記憶體洩露
- js記憶體洩露JS記憶體洩露
- JavaScript記憶體洩露JavaScript記憶體洩露
- 記憶體洩露嗎記憶體洩露
- SHBrowseForFolder 記憶體洩露記憶體洩露
- 記一次"記憶體洩露"排查過程記憶體洩露
- 記憶體溢位和記憶體洩露記憶體溢位記憶體洩露
- ArkTS 的記憶體快照與記憶體洩露除錯記憶體洩露除錯
- Lowmemorykiller記憶體洩露分析記憶體洩露
- 一次Kafka記憶體洩露排查經過Kafka記憶體洩露
- 使用 mtrace 分析 “記憶體洩露”記憶體洩露
- 實戰Go記憶體洩露Go記憶體洩露
- js記憶體洩露的原因JS記憶體洩露
- Java記憶體洩露的原因Java記憶體洩露
- JAVA 記憶體洩露的理解Java記憶體洩露
- IE中的記憶體洩露記憶體洩露
- 學習Java:記憶體洩露Java記憶體洩露
- PHP的記憶體洩露問題與垃圾回收PHP記憶體洩露
- 記一次Go websocket 專案記憶體洩露排查 + 使用Go pprof定位記憶體洩露GoWeb記憶體洩露
- Android 記憶體洩露詳解Android記憶體洩露
- 線上記憶體洩露定位--memleak工具記憶體洩露
- Pprof定位Go程式記憶體洩露Go記憶體洩露
- Android 檢測記憶體洩露Android記憶體洩露
- 如何處理 JavaScript 記憶體洩露JavaScript記憶體洩露
- leaks工具查詢記憶體洩露記憶體洩露
- 記憶體洩露引起的問題記憶體洩露
- 如何定位和解決記憶體洩露記憶體洩露
- JavaScript中的記憶體洩露模式JavaScript記憶體洩露模式
- ThreaLocal記憶體洩露的問題記憶體洩露
- 如何避免JavaScript的記憶體洩露及記憶體管理技巧JavaScript記憶體洩露
- Linux記憶體洩露案例分析和記憶體管理分享Linux記憶體洩露
- win10驅動記憶體洩露如何解決_win10記憶體洩露處理方法Win10記憶體洩露
- nodejs爬蟲記憶體洩露排查NodeJS爬蟲記憶體洩露
- 乾貨分享:淺談記憶體洩露記憶體洩露