FlatBuffers

Google開発のメモリ効率的なクロスプラットフォーム シリアライゼーション ライブラリ

serializationbinaryperformancecross-platformzero-copy

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年のベンチマーク結果

形式シリアライゼーション時間デシリアライゼーション時間メモリ使用量
FlatBuffers711.2 nsゼロコピー
Protocol Buffers1,827 ns高速
JSON7,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`);

他のライブラリとの比較

特徴FlatBuffersProtocol BuffersJSONMessagePack
パフォーマンス最高
メモリ効率最高
可読性
スキーマ進化優秀優秀柔軟制限的
バイナリサイズ
学習コスト

最適化のヒント

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;
}

トラブルシューティング

よくある問題と解決策

  1. メモリ不足エラー

    // 解決策: 適切なバッファサイズを設定
    const builder = new flatbuffers.Builder(4096);
    
  2. 文字列エンコーディング問題

    // 解決策: UTF-8エンコーディングを明示
    const str = builder.createString('日本語テキスト');
    
  3. ベクター順序の問題

    // 解決策: 逆順でベクターを作成
    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と比較して、ゼロコピー アクセスによる圧倒的な速度と効率性を提供します。学習コストは高めですが、パフォーマンス要求の高いアプリケーションでは投資に見合った価値があります。