FlatBuffers
ライブラリ
FlatBuffers
概要
FlatBuffersは、Google開発の高性能クロスプラットフォーム・シリアライゼーションライブラリです。最大の特徴は「ゼロコピー・デシリアライゼーション」で、デシリアライゼーション時にメモリコピーが不要です。ゲーム開発者Wouter van Oortmerssenが開発し、Cocos2d-x、Facebook Android、多数のゲーム開発で使用されており、メモリ効率とアクセス速度が重要なモバイルアプリ・ゲーム・リアルタイムシステムで特に威力を発揮します。
詳細
FlatBuffers 2025年版は、メモリ効率を極限まで追求したフラットバイナリ形式の設計により、従来のシリアライゼーション形式を大幅に上回る性能を実現しています。スキーマ駆動型の構造により、フィールドアクセス時の型安全性を保証し、Protocol Buffersと比較して一部のフィールドにのみアクセスする場合99%以上のデータを無視できるため、大幅な処理速度向上とメモリ使用量削減を達成します。コードジェネレーター(flatc)により、15以上の言語で統一されたAPIを提供し、エンタープライズ開発における多言語対応も万全です。
主な特徴
- ゼロコピー・デシリアライゼーション: デシリアライゼーション時のメモリコピー不要
- 超高速アクセス: フラットメモリレイアウトによる最高レベルのアクセス速度
- スキーマベース: 厳密な型安全性とバージョン管理
- 省メモリ: アクセス時の一時オブジェクト生成なし
- 多言語対応: 15以上の言語でAPI統一
- 企業実績: Google、Facebook、ゲーム業界での採用実績
メリット・デメリット
メリット
- 圧倒的なアクセス速度とメモリ効率(ゼロコピー・デシリアライゼーション)
- 部分アクセス時の高速性(必要フィールドのみアクセス可能)
- フラットメモリレイアウトによる高いキャッシュ効率
- スキーマベースによる厳密な型安全性と後方互換性の保証
- モバイルデバイスでのバッテリー消費削減と高速化
- ゲーム開発・リアルタイムシステムでの豊富な実績
デメリット
- 実装コード量がJSON/Protocol Buffersより多く複雑
- 一部のミューテーション操作が不可能(読み取り専用)
- スキーマ進化の柔軟性がProtocol Buffersより限定的
- フィールド名変更・削除の制約が大きい
- 小規模データでは恩恵が限定的
- デバッグ時のバイナリ形式による可読性の低さ
参考ページ
書き方の例
基本的なセットアップ
# FlatBuffersコンパイラのインストール
# Ubuntu/Debian
sudo apt-get install flatbuffers-compiler
# macOS
brew install flatbuffers
# Windows
choco install flatbuffers
# ソースからビルド
git clone https://github.com/google/flatbuffers.git
cd flatbuffers
cmake -G "Unix Makefiles" -DCMAKE_BUILD_TYPE=Release
make -j4
# 言語バインディングのインストール
# C++ (システムに含まれている場合が多い)
# Python
pip install flatbuffers
# Java/Kotlin
# Maven
# <dependency>
# <groupId>com.google.flatbuffers</groupId>
# <artifactId>flatbuffers-java</artifactId>
# <version>24.3.25</version>
# </dependency>
# JavaScript/Node.js
npm install flatbuffers
# C#
dotnet add package Google.FlatBuffers
# Go
go get github.com/google/flatbuffers/go
スキーマ定義とコード生成
// monster.fbs - FlatBuffersスキーマファイル
namespace MyGame.Sample;
enum Color : byte { Red = 0, Green, Blue = 2 }
union Equipment { Weapon }
struct Vec3 {
x:float;
y:float;
z:float;
}
table Monster {
pos:Vec3; // 構造体フィールド
mana:short = 150; // デフォルト値付きフィールド
hp:short = 100;
name:string; // 文字列フィールド
friendly:bool = false (deprecated); // 非推奨フィールド
inventory:[ubyte]; // バイト配列
color:Color = Blue; // 列挙型
weapons:[Weapon]; // テーブル配列
equipped:Equipment; // ユニオン型
path:[Vec3]; // 構造体配列
}
table Weapon {
name:string;
damage:short;
}
root_type Monster; // ルート型の指定
# スキーマからコード生成
flatc --cpp --python --java --csharp --go monster.fbs
# 特定言語のみ
flatc --cpp monster.fbs # C++
flatc --python monster.fbs # Python
flatc --java monster.fbs # Java
flatc --js monster.fbs # JavaScript
flatc --ts monster.fbs # TypeScript
# 出力ディレクトリ指定
flatc --cpp --python -o ./generated monster.fbs
# JSONからバイナリ形式へ変換
flatc --binary monster.fbs monster.json
# バイナリからJSONへ変換(デバッグ用)
flatc --json monster.fbs monster.bin
C++での基本的な使用例
#include "flatbuffers/flatbuffers.h"
#include "monster_generated.h" // 生成されたヘッダ
using namespace MyGame::Sample;
void createMonster() {
flatbuffers::FlatBufferBuilder builder(1024);
// 文字列の作成
auto weapon_one_name = builder.CreateString("Sword");
auto weapon_two_name = builder.CreateString("Axe");
auto monster_name = builder.CreateString("Orc");
// Weaponテーブルの作成
short weapon_one_damage = 3;
short weapon_two_damage = 5;
auto sword = CreateWeapon(builder, weapon_one_name, weapon_one_damage);
auto axe = CreateWeapon(builder, weapon_two_name, weapon_two_damage);
// weaponsベクターの作成
std::vector<flatbuffers::Offset<Weapon>> weapons_vector;
weapons_vector.push_back(sword);
weapons_vector.push_back(axe);
auto weapons = builder.CreateVector(weapons_vector);
// inventoryベクターの作成
unsigned char treasure[] = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9};
auto inventory = builder.CreateVector(treasure, 10);
// pathベクター(構造体配列)の作成
Vec3 points[] = { Vec3(1.0f, 2.0f, 3.0f), Vec3(4.0f, 5.0f, 6.0f) };
auto path = builder.CreateVectorOfStructs(points, 2);
// Monsterテーブルの作成
Vec3 position(1.0f, 2.0f, 3.0f);
auto orc = CreateMonster(builder,
&position, // pos
150, // mana
80, // hp
monster_name, // name
inventory, // inventory
Color_Red, // color
weapons, // weapons
Equipment_Weapon, // equipped_type
axe.Union(), // equipped
path); // path
// バッファを完成
builder.Finish(orc);
// シリアライズされたデータを取得
uint8_t *buffer_pointer = builder.GetBufferPointer();
int buffer_size = builder.GetSize();
printf("FlatBuffer created: %d bytes\n", buffer_size);
}
void readMonster(const uint8_t* buffer) {
// ゼロコピーでアクセス開始
auto monster = GetMonster(buffer);
// 基本フィールドへの直接アクセス
printf("Monster HP: %d\n", monster->hp());
printf("Monster Mana: %d\n", monster->mana());
printf("Monster Name: %s\n", monster->name()->c_str());
printf("Monster Color: %d\n", static_cast<int>(monster->color()));
// 位置情報(構造体)へのアクセス
auto pos = monster->pos();
if (pos) {
printf("Position: (%.2f, %.2f, %.2f)\n", pos->x(), pos->y(), pos->z());
}
// inventoryベクターへのアクセス
auto inventory = monster->inventory();
if (inventory) {
printf("Inventory items: ");
for (unsigned int i = 0; i < inventory->size(); i++) {
printf("%d ", inventory->Get(i));
}
printf("\n");
}
// weaponsベクター(テーブル配列)へのアクセス
auto weapons = monster->weapons();
if (weapons) {
printf("Weapons count: %u\n", weapons->size());
for (unsigned int i = 0; i < weapons->size(); i++) {
auto weapon = weapons->Get(i);
printf("Weapon %u: %s (damage: %d)\n",
i, weapon->name()->c_str(), weapon->damage());
}
}
// ユニオン型へのアクセス
if (monster->equipped_type() == Equipment_Weapon) {
auto equipped_weapon = monster->equipped_as_Weapon();
printf("Equipped: %s (damage: %d)\n",
equipped_weapon->name()->c_str(), equipped_weapon->damage());
}
// path構造体配列へのアクセス
auto path = monster->path();
if (path) {
printf("Path points: %u\n", path->size());
for (unsigned int i = 0; i < path->size(); i++) {
auto point = path->Get(i);
printf("Point %u: (%.2f, %.2f, %.2f)\n",
i, point->x(), point->y(), point->z());
}
}
}
int main() {
// Monster作成とシリアライゼーション
flatbuffers::FlatBufferBuilder builder(1024);
// 作成処理...(上記createMonster()の内容)
// 読み取り処理
readMonster(builder.GetBufferPointer());
return 0;
}
Pythonでの使用例
import flatbuffers
import MyGame.Sample.Monster as Monster
import MyGame.Sample.Weapon as Weapon
import MyGame.Sample.Vec3 as Vec3
import MyGame.Sample.Color as Color
import MyGame.Sample.Equipment as Equipment
def create_monster():
"""Monsterの作成とシリアライゼーション"""
builder = flatbuffers.Builder(1024)
# 文字列の作成
weapon_one_name = builder.CreateString("Sword")
weapon_two_name = builder.CreateString("Axe")
monster_name = builder.CreateString("Orc")
# Weaponテーブルの作成
Weapon.WeaponStart(builder)
Weapon.WeaponAddName(builder, weapon_one_name)
Weapon.WeaponAddDamage(builder, 3)
sword = Weapon.WeaponEnd(builder)
Weapon.WeaponStart(builder)
Weapon.WeaponAddName(builder, weapon_two_name)
Weapon.WeaponAddDamage(builder, 5)
axe = Weapon.WeaponEnd(builder)
# weaponsベクターの作成
Monster.MonsterStartWeaponsVector(builder, 2)
builder.PrependUOffsetTRelative(axe) # 逆順で追加
builder.PrependUOffsetTRelative(sword)
weapons = builder.EndVector(2)
# inventoryベクターの作成
Monster.MonsterStartInventoryVector(builder, 10)
for i in reversed(range(10)): # 逆順で追加
builder.PrependByte(i)
inventory = builder.EndVector(10)
# pathベクターの作成(構造体配列)
Monster.MonsterStartPathVector(builder, 2)
Vec3.CreateVec3(builder, 4.0, 5.0, 6.0) # 逆順で追加
Vec3.CreateVec3(builder, 1.0, 2.0, 3.0)
path = builder.EndVector(2)
# Monsterテーブルの作成
Monster.MonsterStart(builder)
Monster.MonsterAddPos(builder, Vec3.CreateVec3(builder, 1.0, 2.0, 3.0))
Monster.MonsterAddHp(builder, 80)
Monster.MonsterAddMana(builder, 150)
Monster.MonsterAddName(builder, monster_name)
Monster.MonsterAddInventory(builder, inventory)
Monster.MonsterAddColor(builder, Color.Color.Red)
Monster.MonsterAddWeapons(builder, weapons)
Monster.MonsterAddEquippedType(builder, Equipment.Equipment.Weapon)
Monster.MonsterAddEquipped(builder, axe)
Monster.MonsterAddPath(builder, path)
orc = Monster.MonsterEnd(builder)
# バッファを完成
builder.Finish(orc)
# シリアライズされたバイト列を取得
buf = builder.Output()
print(f"FlatBuffer created: {len(buf)} bytes")
return buf
def read_monster(buf):
"""Monsterの読み取りとアクセス"""
# ゼロコピーでアクセス開始
monster = Monster.Monster.GetRootAs(buf, 0)
# 基本フィールドへの直接アクセス
print(f"Monster HP: {monster.Hp()}")
print(f"Monster Mana: {monster.Mana()}")
print(f"Monster Name: {monster.Name().decode('utf-8')}")
print(f"Monster Color: {monster.Color()}")
# 位置情報(構造体)へのアクセス
pos = monster.Pos()
if pos:
print(f"Position: ({pos.X():.2f}, {pos.Y():.2f}, {pos.Z():.2f})")
# inventoryベクターへのアクセス
print("Inventory items:", end=" ")
for i in range(monster.InventoryLength()):
print(monster.Inventory(i), end=" ")
print()
# weaponsベクター(テーブル配列)へのアクセス
print(f"Weapons count: {monster.WeaponsLength()}")
for i in range(monster.WeaponsLength()):
weapon = monster.Weapons(i)
weapon_name = weapon.Name().decode('utf-8') if weapon.Name() else "Unknown"
print(f"Weapon {i}: {weapon_name} (damage: {weapon.Damage()})")
# ユニオン型へのアクセス
if monster.EquippedType() == Equipment.Equipment.Weapon:
equipped_weapon = Weapon.Weapon()
equipped_weapon.Init(monster.Equipped().Bytes, monster.Equipped().Pos)
weapon_name = equipped_weapon.Name().decode('utf-8') if equipped_weapon.Name() else "Unknown"
print(f"Equipped: {weapon_name} (damage: {equipped_weapon.Damage()})")
# path構造体配列へのアクセス
print(f"Path points: {monster.PathLength()}")
for i in range(monster.PathLength()):
point = monster.Path(i)
print(f"Point {i}: ({point.X():.2f}, {point.Y():.2f}, {point.Z():.2f})")
# 使用例
if __name__ == "__main__":
# Monster作成とシリアライゼーション
buffer = create_monster()
# 読み取り処理
read_monster(buffer)
# ファイル保存例
with open("monster.bin", "wb") as f:
f.write(buffer)
# ファイル読み込み例
with open("monster.bin", "rb") as f:
loaded_buffer = f.read()
print("\n=== Loaded from file ===")
read_monster(loaded_buffer)
パフォーマンス最適化と実践的なテクニック
#include "flatbuffers/flatbuffers.h"
#include "monster_generated.h"
#include <chrono>
#include <vector>
#include <memory>
using namespace MyGame::Sample;
class FlatBufferPerformanceDemo {
private:
std::unique_ptr<flatbuffers::FlatBufferBuilder> builder_;
std::vector<uint8_t> buffer_pool_;
public:
FlatBufferPerformanceDemo() : builder_(std::make_unique<flatbuffers::FlatBufferBuilder>(65536)) {}
// 高速バッチ処理
void createMonstersInBatch(int count) {
auto start = std::chrono::high_resolution_clock::now();
// バッファのクリアと再利用
builder_->Clear();
// 一括でモンスター配列を作成
std::vector<flatbuffers::Offset<Monster>> monsters;
monsters.reserve(count);
for (int i = 0; i < count; i++) {
// 文字列のプール化(同じ名前を再利用)
static auto cached_names = std::vector<flatbuffers::Offset<flatbuffers::String>>();
if (cached_names.empty()) {
cached_names.push_back(builder_->CreateString("Orc"));
cached_names.push_back(builder_->CreateString("Goblin"));
cached_names.push_back(builder_->CreateString("Troll"));
}
auto monster_name = cached_names[i % cached_names.size()];
// 最小限のフィールドでモンスター作成
Vec3 position(static_cast<float>(i), static_cast<float>(i * 2), 0.0f);
auto monster = CreateMonster(*builder_,
&position,
static_cast<short>(100 + i), // mana
static_cast<short>(80 + i), // hp
monster_name,
0, // inventory (null)
Color_Red,
0, // weapons (null)
Equipment_NONE,
0, // equipped (null)
0); // path (null)
monsters.push_back(monster);
}
// モンスター配列をFlatBufferに格納
auto monsters_vector = builder_->CreateVector(monsters);
// ルートテーブルとして配列を保存(擬似的なコンテナテーブル)
flatbuffers::Offset<flatbuffers::Vector<flatbuffers::Offset<Monster>>> root;
builder_->Finish(monsters_vector);
auto end = std::chrono::high_resolution_clock::now();
auto duration = std::chrono::duration_cast<std::chrono::microseconds>(end - start);
printf("Created %d monsters in %ld microseconds\n", count, duration.count());
printf("Buffer size: %d bytes\n", builder_->GetSize());
printf("Average bytes per monster: %.2f\n",
static_cast<double>(builder_->GetSize()) / count);
}
// 部分アクセスのパフォーマンステスト
void benchmarkPartialAccess() {
createMonstersInBatch(10000);
const uint8_t* buffer = builder_->GetBufferPointer();
auto monsters_vector = flatbuffers::GetRoot<flatbuffers::Vector<flatbuffers::Offset<Monster>>>(buffer);
auto start = std::chrono::high_resolution_clock::now();
// 1000体のモンスターのHPのみアクセス(99%のデータを無視)
int total_hp = 0;
for (unsigned int i = 0; i < std::min(1000u, monsters_vector->size()); i++) {
auto monster = monsters_vector->Get(i);
total_hp += monster->hp(); // HPフィールドのみアクセス
}
auto end = std::chrono::high_resolution_clock::now();
auto duration = std::chrono::duration_cast<std::chrono::nanoseconds>(end - start);
printf("Partial access (HP only) for 1000 monsters: %ld nanoseconds\n", duration.count());
printf("Average time per access: %.2f nanoseconds\n",
static_cast<double>(duration.count()) / 1000);
printf("Total HP: %d\n", total_hp);
}
// メモリ使用量分析
void analyzeMemoryUsage() {
const int monster_count = 1000;
createMonstersInBatch(monster_count);
const uint8_t* buffer = builder_->GetBufferPointer();
int buffer_size = builder_->GetSize();
printf("\n=== Memory Usage Analysis ===\n");
printf("Monster count: %d\n", monster_count);
printf("Total buffer size: %d bytes\n", buffer_size);
printf("Average size per monster: %.2f bytes\n",
static_cast<double>(buffer_size) / monster_count);
// アクセス時のメモリ使用量(ほぼゼロ)
printf("Additional memory for access: ~0 bytes (zero-copy)\n");
// 比較: 通常のオブジェクトであれば
struct NormalMonster {
Vec3 pos;
short mana, hp;
std::string name;
Color color;
};
printf("Estimated memory for %d normal objects: %lu bytes\n",
monster_count, monster_count * sizeof(NormalMonster));
printf("Memory efficiency ratio: %.2fx\n",
static_cast<double>(monster_count * sizeof(NormalMonster)) / buffer_size);
}
// リアルタイムアプリケーション向け最適化
void simulateRealtimeUsage() {
printf("\n=== Realtime Usage Simulation ===\n");
const int frame_count = 1000; // 1000フレーム分
const int monsters_per_frame = 100;
auto total_start = std::chrono::high_resolution_clock::now();
for (int frame = 0; frame < frame_count; frame++) {
auto frame_start = std::chrono::high_resolution_clock::now();
// フレームごとに新しいモンスターデータを作成
builder_->Clear(); // 高速なバッファクリア
std::vector<flatbuffers::Offset<Monster>> frame_monsters;
frame_monsters.reserve(monsters_per_frame);
for (int i = 0; i < monsters_per_frame; i++) {
Vec3 position(
static_cast<float>(i + frame * 0.1f),
static_cast<float>(i * 2 + frame * 0.2f),
0.0f
);
auto monster_name = builder_->CreateString("FrameMonster");
auto monster = CreateMonster(*builder_,
&position,
static_cast<short>(100 + i),
static_cast<short>(80 + i),
monster_name);
frame_monsters.push_back(monster);
}
auto monsters_vector = builder_->CreateVector(frame_monsters);
builder_->Finish(monsters_vector);
// データ読み取り(ゲームループの処理をシミュレート)
const uint8_t* buffer = builder_->GetBufferPointer();
auto monsters = flatbuffers::GetRoot<flatbuffers::Vector<flatbuffers::Offset<Monster>>>(buffer);
// 全モンスターの位置を更新(読み取り処理)
float total_distance = 0.0f;
for (unsigned int i = 0; i < monsters->size(); i++) {
auto monster = monsters->Get(i);
auto pos = monster->pos();
if (pos) {
total_distance += std::sqrt(pos->x() * pos->x() + pos->y() * pos->y());
}
}
auto frame_end = std::chrono::high_resolution_clock::now();
auto frame_duration = std::chrono::duration_cast<std::chrono::microseconds>(
frame_end - frame_start);
// 16.67ms (60FPS) 以下かチェック
if (frame % 100 == 0) {
printf("Frame %d: %ld μs (%.2f FPS equivalent)\n",
frame, frame_duration.count(),
1000000.0 / frame_duration.count());
}
}
auto total_end = std::chrono::high_resolution_clock::now();
auto total_duration = std::chrono::duration_cast<std::chrono::milliseconds>(
total_end - total_start);
printf("Total time for %d frames: %ld ms\n", frame_count, total_duration.count());
printf("Average FPS: %.2f\n",
static_cast<double>(frame_count * 1000) / total_duration.count());
}
};
int main() {
FlatBufferPerformanceDemo demo;
// パフォーマンステスト実行
demo.createMonstersInBatch(10000);
demo.benchmarkPartialAccess();
demo.analyzeMemoryUsage();
demo.simulateRealtimeUsage();
return 0;
}
JSONとFlatBuffersの相互変換
# monster.json - サンプルJSONデータ
cat > monster.json << 'EOF'
{
"pos": {
"x": 1.0,
"y": 2.0,
"z": 3.0
},
"hp": 80,
"mana": 150,
"name": "Orc",
"color": "Red",
"inventory": [0, 1, 2, 3, 4, 5, 6, 7, 8, 9],
"weapons": [
{
"name": "Sword",
"damage": 3
},
{
"name": "Axe",
"damage": 5
}
],
"equipped_type": "Weapon",
"equipped": {
"name": "Axe",
"damage": 5
},
"path": [
{ "x": 1.0, "y": 2.0, "z": 3.0 },
{ "x": 4.0, "y": 5.0, "z": 6.0 }
]
}
EOF
# JSONからFlatBufferバイナリ形式に変換
flatc --binary monster.fbs monster.json
# バイナリからJSONに変換(デバッグ・検証用)
flatc --json monster.fbs monster.bin
# バイナリファイルのサイズ確認
ls -la monster.bin
# JSON vs FlatBufferのサイズ比較
echo "JSON size: $(wc -c < monster.json) bytes"
echo "FlatBuffer size: $(wc -c < monster.bin) bytes"
# 16進ダンプ(バイナリ構造確認)
hexdump -C monster.bin | head -10
高度なスキーマ進化とベストプラクティス
// version1.fbs - 初期バージョン
namespace MyGame.Sample;
table Monster {
hp:short = 100;
mana:short = 150;
name:string;
}
root_type Monster;
// version2.fbs - 安全な進化例
namespace MyGame.Sample;
table Monster {
hp:short = 100;
mana:short = 150;
name:string;
// 新しいフィールドは末尾に追加(安全)
level:short = 1;
experience:int = 0;
// 非推奨フィールドをマーク
old_field:int (deprecated);
}
root_type Monster;
// version3.fbs - IDを使った安全な順序変更
namespace MyGame.Sample;
table Monster {
// IDを明示的に指定して順序変更を安全に
level:short = 1 (id: 3);
hp:short = 100 (id: 0);
experience:int = 0 (id: 4);
mana:short = 150 (id: 1);
name:string (id: 2);
// 新機能
skills:[string] (id: 5);
metadata:[ubyte] (id: 6);
}
root_type Monster;
// バージョン間の互換性チェック
#include "flatbuffers/flatbuffers.h"
void checkCompatibility(const uint8_t* buffer) {
auto monster = GetMonster(buffer);
// 古いフィールドは常にアクセス可能
printf("HP: %d\n", monster->hp());
printf("Name: %s\n", monster->name() ? monster->name()->c_str() : "null");
// 新しいフィールドの存在チェック
printf("Level: %d\n", monster->level()); // デフォルト値が返される
// デフォルト値との比較でフィールドの存在をチェック
if (monster->level() != 1) { // デフォルト値と異なる場合
printf("Level field was explicitly set to: %d\n", monster->level());
}
// コレクションフィールドの安全なアクセス
auto skills = monster->skills();
if (skills) {
printf("Skills count: %u\n", skills->size());
for (unsigned int i = 0; i < skills->size(); i++) {
printf("Skill %u: %s\n", i, skills->Get(i)->c_str());
}
} else {
printf("No skills field present in this version\n");
}
}