FlatBuffers
Google開発のメモリ効率的なクロスプラットフォーム シリアライゼーション ライブラリ
FlatBuffers
FlatBuffers は、Google が開発したメモリ効率的なクロスプラットフォーム シリアライゼーション ライブラリです。ゼロコピー アクセスを実現し、パフォーマンスが重要なアプリケーションに最適化されています。
主な特徴
- ゼロコピー アクセス: パースや解凍なしでデータに直接アクセス可能
- 高いパフォーマンス: JSONやProtocol Buffersよりも高速
- メモリ効率: バッファのメモリのみでアクセス可能
- クロスプラットフォーム: C++、Java、JavaScript、Python、Rust、Swift等をサポート
- 前方・後方互換性: スキーマの変更に対応
- 型安全性: 静的型付けによる安全なデータアクセス
主なユースケース
- ゲーム開発: リアルタイムでのデータ転送
- モバイルアプリ: メモリ効率が重要なアプリケーション
- IoT デバイス: 低リソース環境での高速処理
- 通信システム: 高頻度でのデータ交換
- データベース: 高速なシリアライゼーション
- API通信: 効率的なバイナリ通信
インストール
npm install flatbuffers
CDNから直接利用:
<script src="https://unpkg.com/flatbuffers@latest/js/flatbuffers.js"></script>
基本的な使い方
1. スキーマの定義
monster.fbsファイルでスキーマを定義:
namespace MyGame.Sample;
enum Color:byte { Red = 0, Green, Blue }
union Equipment { Weapon }
struct Vec3 {
x:float;
y:float;
z:float;
}
table Weapon {
name:string;
damage:short;
}
table Monster {
pos:Vec3;
hp:short = 100;
mana:short = 150;
name:string;
inventory:[ubyte];
color:Color = Blue;
weapons:[Weapon];
equipped:Equipment;
path:[Vec3];
}
root_type Monster;
2. コード生成
# JavaScript コード生成
flatc --js monster.fbs
# TypeScript コード生成
flatc --ts monster.fbs
3. データの作成
import * as flatbuffers from 'flatbuffers';
import { MyGame } from './monster_generated';
// ビルダーを初期化
const builder = new flatbuffers.Builder(1024);
// 文字列の作成
const weaponName = builder.createString('Sword');
const monsterName = builder.createString('Orc');
// Weaponテーブルの作成
MyGame.Sample.Weapon.startWeapon(builder);
MyGame.Sample.Weapon.addName(builder, weaponName);
MyGame.Sample.Weapon.addDamage(builder, 3);
const sword = MyGame.Sample.Weapon.endWeapon(builder);
// インベントリベクターの作成
const treasure = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9];
const inv = MyGame.Sample.Monster.createInventoryVector(builder, treasure);
// 武器ベクターの作成
const weapons = MyGame.Sample.Monster.createWeaponsVector(builder, [sword]);
// Monsterテーブルの作成
MyGame.Sample.Monster.startMonster(builder);
MyGame.Sample.Monster.addPos(builder,
MyGame.Sample.Vec3.createVec3(builder, 1.0, 2.0, 3.0));
MyGame.Sample.Monster.addHp(builder, 300);
MyGame.Sample.Monster.addColor(builder, MyGame.Sample.Color.Red);
MyGame.Sample.Monster.addName(builder, monsterName);
MyGame.Sample.Monster.addInventory(builder, inv);
MyGame.Sample.Monster.addWeapons(builder, weapons);
const orc = MyGame.Sample.Monster.endMonster(builder);
// バッファの取得
builder.finish(orc);
const buf = builder.asUint8Array();
4. データの読み込み
// バイナリデータからの読み込み
const data = new Uint8Array(fs.readFileSync('monster.dat'));
const buf = new flatbuffers.ByteBuffer(data);
// ルートオブジェクトの取得
const monster = MyGame.Sample.Monster.getRootAsMonster(buf);
// データへのアクセス
const hp = monster.hp();
const mana = monster.mana();
const name = monster.name();
const pos = monster.pos();
const x = pos.x();
const y = pos.y();
const z = pos.z();
// ベクターデータへのアクセス
const weaponsLength = monster.weaponsLength();
const weapon = monster.weapons(0);
const weaponName = weapon.name();
const weaponDamage = weapon.damage();
// インベントリベクターへのアクセス
const invLength = monster.inventoryLength();
const item = monster.inventory(0);
5. ブラウザでの使用
<!DOCTYPE html>
<html>
<head>
<script src="flatbuffers.js"></script>
<script src="monster_generated.js"></script>
</head>
<body>
<input type="file" id="file_input" onchange="readFile();">
<script>
function readFile() {
const reader = new FileReader();
const file = document.getElementById('file_input').files[0];
reader.onload = function() {
const data = new Uint8Array(reader.result);
const buf = new flatbuffers.ByteBuffer(data);
const monster = MyGame.Sample.Monster.getRootAsMonster(buf);
console.log('HP:', monster.hp());
console.log('Name:', monster.name());
};
reader.readAsArrayBuffer(file);
}
</script>
</body>
</html>
高度な使用例
TypeScript での使用
import * as flatbuffers from 'flatbuffers';
import { MyGame } from './monster_generated';
// オブジェクトベース API
const monsterobj = new MyGame.Sample.MonsterT();
// デシリアライゼーション
const buffer = new flatbuffers.ByteBuffer(data);
const monster = MyGame.Sample.Monster.getRootAsMonster(buffer);
monster.unpackTo(monsterobj);
// オブジェクトの更新
monsterobj.name = "Bob";
monsterobj.hp = 200;
// 再シリアライゼーション
const fbb = new flatbuffers.Builder(1024);
const newMonster = MyGame.Sample.Monster.finishMonsterBuffer(fbb, monsterobj.pack(fbb));
高度なベクター処理
// 複数のベクターを含むデータの作成
const pathVector = [];
for (let i = 0; i < 10; i++) {
pathVector.push(MyGame.Sample.Vec3.createVec3(builder, i, i*2, i*3));
}
MyGame.Sample.Monster.startPathVector(builder, pathVector.length);
for (let i = pathVector.length - 1; i >= 0; i--) {
builder.addOffset(pathVector[i]);
}
const path = builder.endVector();
// パスベクターの読み込み
const pathLength = monster.pathLength();
for (let i = 0; i < pathLength; i++) {
const point = monster.path(i);
console.log(`Point ${i}: (${point.x()}, ${point.y()}, ${point.z()})`);
}
パフォーマンス比較
2024年のベンチマーク結果
| 形式 | シリアライゼーション時間 | デシリアライゼーション時間 | メモリ使用量 |
|---|---|---|---|
| FlatBuffers | 711.2 ns | ゼロコピー | 低 |
| Protocol Buffers | 1,827 ns | 高速 | 中 |
| JSON | 7,045 ns | 高速 | 高 |
実測例
// パフォーマンス測定
const start = performance.now();
// FlatBuffers: 3-4 ms
const monster = MyGame.Sample.Monster.getRootAsMonster(buffer);
const hp = monster.hp();
const end = performance.now();
console.log(`FlatBuffers read time: ${end - start} ms`);
// JSON比較: 30-40 ms
const jsonStart = performance.now();
const jsonData = JSON.parse(jsonString);
const jsonEnd = performance.now();
console.log(`JSON parse time: ${jsonEnd - jsonStart} ms`);
他のライブラリとの比較
| 特徴 | FlatBuffers | Protocol Buffers | JSON | MessagePack |
|---|---|---|---|---|
| パフォーマンス | 最高 | 高 | 中 | 高 |
| メモリ効率 | 最高 | 高 | 低 | 高 |
| 可読性 | 低 | 中 | 高 | 低 |
| スキーマ進化 | 優秀 | 優秀 | 柔軟 | 制限的 |
| バイナリサイズ | 小 | 小 | 大 | 小 |
| 学習コスト | 高 | 中 | 低 | 低 |
最適化のヒント
1. メモリ効率の向上
// バッファサイズの最適化
const builder = new flatbuffers.Builder(0); // 必要に応じて拡張
// 不要なデータの除外
MyGame.Sample.Monster.startMonster(builder);
// 必要なフィールドのみ追加
MyGame.Sample.Monster.addHp(builder, 100);
MyGame.Sample.Monster.addName(builder, name);
const monster = MyGame.Sample.Monster.endMonster(builder);
2. 再利用可能なビルダー
class MonsterBuilder {
constructor() {
this.builder = new flatbuffers.Builder(1024);
}
createMonster(name, hp, position) {
this.builder.clear();
const nameOffset = this.builder.createString(name);
MyGame.Sample.Monster.startMonster(this.builder);
MyGame.Sample.Monster.addName(this.builder, nameOffset);
MyGame.Sample.Monster.addHp(this.builder, hp);
MyGame.Sample.Monster.addPos(this.builder, position);
const monster = MyGame.Sample.Monster.endMonster(this.builder);
this.builder.finish(monster);
return this.builder.asUint8Array();
}
}
3. バッチ処理
// 複数のオブジェクトを効率的に処理
function createMonsterBatch(monsters) {
const builder = new flatbuffers.Builder(1024 * monsters.length);
const monsterOffsets = [];
for (const monsterData of monsters) {
const nameOffset = builder.createString(monsterData.name);
MyGame.Sample.Monster.startMonster(builder);
MyGame.Sample.Monster.addName(builder, nameOffset);
MyGame.Sample.Monster.addHp(builder, monsterData.hp);
monsterOffsets.push(MyGame.Sample.Monster.endMonster(builder));
}
return monsterOffsets;
}
トラブルシューティング
よくある問題と解決策
-
メモリ不足エラー
// 解決策: 適切なバッファサイズを設定 const builder = new flatbuffers.Builder(4096); -
文字列エンコーディング問題
// 解決策: UTF-8エンコーディングを明示 const str = builder.createString('日本語テキスト'); -
ベクター順序の問題
// 解決策: 逆順でベクターを作成 MyGame.Sample.Monster.startWeaponsVector(builder, weapons.length); for (let i = weapons.length - 1; i >= 0; i--) { builder.addOffset(weapons[i]); } const weaponsVector = builder.endVector();
まとめ
FlatBuffers は、パフォーマンスが重要なアプリケーションに最適なシリアライゼーション ライブラリです。特に以下の場合に効果的です:
- リアルタイム処理が必要な場合
- メモリ効率が重要な場合
- 高頻度でのデータ交換が必要な場合
- クロスプラットフォームでの一貫性が必要な場合
JSONやProtocol Buffersと比較して、ゼロコピー アクセスによる圧倒的な速度と効率性を提供します。学習コストは高めですが、パフォーマンス要求の高いアプリケーションでは投資に見合った価値があります。