flatc 生成コードの問題

flatc の生成コードがつらみすぎるので自分でつくったの続きのような関連のようなものです。

flatcが生成したコードは、reader からアクセスした場合は、

固定長のfield 以外は変更ができない。それはなんでかというと

table/struct の構造でnest したものの子供、可変長なものから後ろからかいていくからだ。

なんでこんなことしてるかというと

当然子供のデータのサイズがわからないとその情報書き込んだtable が確定できないからとflatbuffers はそもそもデータが全部相対のオフセットで表現されてるからだ。

書き込んでる途中で当然データがどんどん大きくなるわけだけど。

そもそもgo のslice は

  • 連続領域
  • capacity とlen があり実際はcapacity だけ連続領域がわりあてられているが、len が使われてる長さ
  • capacity より大きい領域が欲しくなれば、まるごとcopy が走る

という構造だ。これは別に問題ではなくそうせざるを得ない。slice もcapacity があることでstack における可能性が出るので。

flatbuffers で問題なのはデータを拡張するときに前から増やしてることだ。

ぶっちゃけこれってcapacity 関係なしにまるごと毎回copyが走らないといけないし、実はgo でのパフォーマンスの問題 も引き起こしている。 c++ とかより(c# とかはしらんw)

fbs-query ではデータのバッファを複数にもっていてずらさないといけない場合は、内部的に複数に分離している。

当然断片化はある程度するわけだけど、書き込み時とかはwritev/sendv すればいいのではと割り切っている。

データサイズの決まってる部分を変更するのは別にデータに変化がないので困らないし、

当然データのサイズがかわった場合に中のデータは相対のオフセットで表現されてるので、

ずらした分だけその情報の書き換えが必要になる。

そのために、データの中で当然挿入する部分をまたいでる参照をdetect することからはじめた

flatbuffers で書き込まれたデータ簡単な可視化

簡単な現在のデータの可視化ができるようにしってみたのだが、

なかなか見やすいし、flatbuffers がどういうふうにserializeしてるのかがよくわかっておもしろい。

schema を以下のようにして

root_type Root;

table Root {
    version:int;
    index:Index;
}

union Index {
  IndexString,
}

table IndexString {
    size:int;
    maps:[InvertedMapString];
}

table InvertedMapString {
    key:string;
    value:Record;
}

struct Record {
    file_id:uint64;
    offset:int64;
    size:int64;
    offset_of_value:int;
    value_size:int;
}

で実際にserialize 済みのデータを簡単に初期化してみた。

  • Type が table/struct/実際の型で
  • Pos がbuffer における先頭からのoffset
{Type:	Root,	Pos:	20}
		{Type:	Int32,	Pos:	32}
		{Type:	Byte,	Pos:	31}
		{Type:	Index,	Pos:	44}
				{Type:	Int32,	Pos:	48}
				{Type:	[]InvertedMapString,	Pos:	44}
						{Type:	InvertedMapString,	Pos:	76}
								{Type:	[]byte,	Pos:	124}
								{Type:	Record,	Pos:	80}
										{Type:	Uint64,	Pos:	80}
										{Type:	Int64,	Pos:	88}
										{Type:	Int64,	Pos:	96}
										{Type:	Int32,	Pos:	104}
										{Type:	Int32,	Pos:	108}
						{Type:	InvertedMapString,	Pos:	140}
								{Type:	[]byte,	Pos:	184}
								{Type:	Record,	Pos:	144}
										{Type:	Uint64,	Pos:	144}
										{Type:	Int64,	Pos:	152}
										{Type:	Int64,	Pos:	160}
										{Type:	Int32,	Pos:	168}
										{Type:	Int32,	Pos:	172}

これを眺めてるとどういうふうに書き込まれてるかはなんとなくわかる。

実際に中のtable とかvtable がどうなってるかは別にだが。 (実際のそのデータのvtable がもつものはpos の前にかかれてtable はそのpos の位置にかかれます。)

これで全体のデータサイズが変わる可能性があるのは

								{Type:	[]byte,	Pos:	124}

とか

				{Type:	[]InvertedMapString,	Pos:	44}

だ。

flatbuffers 的にはvector と呼ばれる配列状態のデータですね。

[]byte のデータ場合は 124 からはじまって 140 より前におわってる.

より小さい文字列を書き込む場合はほっておいてもい.

切り詰めたい場合は,当然あるので、124 あたりにデータが10バイト挿入されるとする時

				{Type:	[]InvertedMapString,	Pos:	44}
                						{Type:	InvertedMapString,	Pos:	140}

この参照があるので 

[]InvertedMapString から InvertedMapString への []InvertedMapString

へのoffset をずらせばいいという感じだ。

実際にflatbuffers ではfiel が0だとvtable だけかかれて、データかかれないので、それ以外の値をいれたら 大きさが変わるとかもあります。(struct は書かれます。vtable 使わないから)