FlatBuffers

SerializationZero CopyHigh PerformanceMemory EfficientBinarySchemaGame DevelopmentGoogle

Library

FlatBuffers

Overview

FlatBuffers is a high-performance cross-platform serialization library developed by Google. Its key feature is "zero-copy deserialization," eliminating the need for memory copying during deserialization. Originally developed by game developer Wouter van Oortmerssen and used in Cocos2d-x, Facebook Android, and numerous game development projects, it particularly excels in mobile apps, games, and real-time systems where memory efficiency and access speed are critical.

Details

FlatBuffers 2025 edition achieves performance that significantly surpasses traditional serialization formats through its flat binary format design that pursues maximum memory efficiency. The schema-driven structure guarantees type safety during field access, and compared to Protocol Buffers, it can ignore over 99% of data when accessing only specific fields, achieving dramatic improvements in processing speed and memory usage reduction. The code generator (flatc) provides unified APIs for over 15 languages, ensuring comprehensive multi-language support for enterprise development.

Key Features

  • Zero-Copy Deserialization: No memory copying required during deserialization
  • Ultra-Fast Access: Highest level access speed through flat memory layout
  • Schema-Based: Strict type safety and version management
  • Memory Efficient: No temporary object creation during access
  • Multi-Language Support: API unification across 15+ languages
  • Enterprise Track Record: Adoption by Google, Facebook, and gaming industry

Pros and Cons

Pros

  • Overwhelming access speed and memory efficiency (zero-copy deserialization)
  • High-speed performance during partial access (access only required fields)
  • High cache efficiency through flat memory layout
  • Strict type safety and backward compatibility guarantee through schema-based approach
  • Reduced battery consumption and acceleration on mobile devices
  • Rich track record in game development and real-time systems

Cons

  • Implementation code is more complex and voluminous than JSON/Protocol Buffers
  • Some mutation operations are impossible (read-only)
  • Schema evolution flexibility is more limited than Protocol Buffers
  • Large constraints on field renaming and deletion
  • Limited benefits for small-scale data
  • Low readability due to binary format during debugging

Reference Pages

Code Examples

Basic Setup

# Installing FlatBuffers compiler
# Ubuntu/Debian
sudo apt-get install flatbuffers-compiler

# macOS
brew install flatbuffers

# Windows
choco install flatbuffers

# Build from source
git clone https://github.com/google/flatbuffers.git
cd flatbuffers
cmake -G "Unix Makefiles" -DCMAKE_BUILD_TYPE=Release
make -j4
# Installing language bindings
# C++ (often included in system)

# 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

Schema Definition and Code Generation

// monster.fbs - FlatBuffers schema file
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;              // struct field
  mana:short = 150;      // field with default value
  hp:short = 100;
  name:string;           // string field
  friendly:bool = false (deprecated);  // deprecated field
  inventory:[ubyte];     // byte array
  color:Color = Blue;    // enum type
  weapons:[Weapon];      // table array
  equipped:Equipment;    // union type
  path:[Vec3];          // struct array
}

table Weapon {
  name:string;
  damage:short;
}

root_type Monster;      // root type specification
# Generate code from schema
flatc --cpp --python --java --csharp --go monster.fbs

# Specific language only
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

# Specify output directory
flatc --cpp --python -o ./generated monster.fbs

# Convert JSON to binary format
flatc --binary monster.fbs monster.json

# Convert binary to JSON (for debugging)
flatc --json monster.fbs monster.bin

Basic Usage in C++

#include "flatbuffers/flatbuffers.h"
#include "monster_generated.h"  // generated header

using namespace MyGame::Sample;

void createMonster() {
    flatbuffers::FlatBufferBuilder builder(1024);
    
    // Create strings
    auto weapon_one_name = builder.CreateString("Sword");
    auto weapon_two_name = builder.CreateString("Axe");
    auto monster_name = builder.CreateString("Orc");
    
    // Create Weapon tables
    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);
    
    // Create weapons vector
    std::vector<flatbuffers::Offset<Weapon>> weapons_vector;
    weapons_vector.push_back(sword);
    weapons_vector.push_back(axe);
    auto weapons = builder.CreateVector(weapons_vector);
    
    // Create inventory vector
    unsigned char treasure[] = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9};
    auto inventory = builder.CreateVector(treasure, 10);
    
    // Create path vector (struct array)
    Vec3 points[] = { Vec3(1.0f, 2.0f, 3.0f), Vec3(4.0f, 5.0f, 6.0f) };
    auto path = builder.CreateVectorOfStructs(points, 2);
    
    // Create Monster table
    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
    
    // Finish buffer
    builder.Finish(orc);
    
    // Get serialized data
    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) {
    // Start zero-copy access
    auto monster = GetMonster(buffer);
    
    // Direct access to basic fields
    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()));
    
    // Access position info (struct)
    auto pos = monster->pos();
    if (pos) {
        printf("Position: (%.2f, %.2f, %.2f)\n", pos->x(), pos->y(), pos->z());
    }
    
    // Access inventory vector
    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");
    }
    
    // Access weapons vector (table array)
    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());
        }
    }
    
    // Access union type
    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());
    }
    
    // Access path struct array
    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 creation and serialization
    flatbuffers::FlatBufferBuilder builder(1024);
    
    // Creation process... (content of createMonster() above)
    
    // Reading process
    readMonster(builder.GetBufferPointer());
    
    return 0;
}

Python Usage Example

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():
    """Create and serialize Monster"""
    builder = flatbuffers.Builder(1024)
    
    # Create strings
    weapon_one_name = builder.CreateString("Sword")
    weapon_two_name = builder.CreateString("Axe")
    monster_name = builder.CreateString("Orc")
    
    # Create Weapon tables
    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)
    
    # Create weapons vector
    Monster.MonsterStartWeaponsVector(builder, 2)
    builder.PrependUOffsetTRelative(axe)    # Add in reverse order
    builder.PrependUOffsetTRelative(sword)
    weapons = builder.EndVector(2)
    
    # Create inventory vector
    Monster.MonsterStartInventoryVector(builder, 10)
    for i in reversed(range(10)):  # Add in reverse order
        builder.PrependByte(i)
    inventory = builder.EndVector(10)
    
    # Create path vector (struct array)
    Monster.MonsterStartPathVector(builder, 2)
    Vec3.CreateVec3(builder, 4.0, 5.0, 6.0)  # Add in reverse order
    Vec3.CreateVec3(builder, 1.0, 2.0, 3.0)
    path = builder.EndVector(2)
    
    # Create Monster table
    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)
    
    # Finish buffer
    builder.Finish(orc)
    
    # Get serialized byte array
    buf = builder.Output()
    print(f"FlatBuffer created: {len(buf)} bytes")
    
    return buf

def read_monster(buf):
    """Read and access Monster"""
    # Start zero-copy access
    monster = Monster.Monster.GetRootAs(buf, 0)
    
    # Direct access to basic fields
    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()}")
    
    # Access position info (struct)
    pos = monster.Pos()
    if pos:
        print(f"Position: ({pos.X():.2f}, {pos.Y():.2f}, {pos.Z():.2f})")
    
    # Access inventory vector
    print("Inventory items:", end=" ")
    for i in range(monster.InventoryLength()):
        print(monster.Inventory(i), end=" ")
    print()
    
    # Access weapons vector (table array)
    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()})")
    
    # Access union type
    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()})")
    
    # Access path struct array
    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})")

# Usage example
if __name__ == "__main__":
    # Monster creation and serialization
    buffer = create_monster()
    
    # Reading process
    read_monster(buffer)
    
    # File save example
    with open("monster.bin", "wb") as f:
        f.write(buffer)
    
    # File load example
    with open("monster.bin", "rb") as f:
        loaded_buffer = f.read()
        print("\n=== Loaded from file ===")
        read_monster(loaded_buffer)

Performance Optimization and Practical Techniques

#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)) {}
    
    // High-speed batch processing
    void createMonstersInBatch(int count) {
        auto start = std::chrono::high_resolution_clock::now();
        
        // Clear and reuse buffer
        builder_->Clear();
        
        // Create monster array in batch
        std::vector<flatbuffers::Offset<Monster>> monsters;
        monsters.reserve(count);
        
        for (int i = 0; i < count; i++) {
            // String pooling (reuse same names)
            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()];
            
            // Create monster with minimal fields
            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);
        }
        
        // Store monster array in FlatBuffer
        auto monsters_vector = builder_->CreateVector(monsters);
        
        // Save as root table
        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);
    }
    
    // Partial access performance test
    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();
        
        // Access only HP of 1000 monsters (ignore 99% of data)
        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();  // Access only HP field
        }
        
        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);
    }
    
    // Memory usage analysis
    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);
        
        // Memory usage during access (almost zero)
        printf("Additional memory for access: ~0 bytes (zero-copy)\n");
        
        // Comparison: with normal objects
        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);
    }
    
    // Real-time application optimization
    void simulateRealtimeUsage() {
        printf("\n=== Realtime Usage Simulation ===\n");
        
        const int frame_count = 1000;  // 1000 frames
        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();
            
            // Create new monster data per frame
            builder_->Clear();  // Fast buffer 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);
            
            // Data reading (simulate game loop processing)
            const uint8_t* buffer = builder_->GetBufferPointer();
            auto monsters = flatbuffers::GetRoot<flatbuffers::Vector<flatbuffers::Offset<Monster>>>(buffer);
            
            // Update all monster positions (reading process)
            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);
            
            // Check if under 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;
    
    // Run performance tests
    demo.createMonstersInBatch(10000);
    demo.benchmarkPartialAccess();
    demo.analyzeMemoryUsage();
    demo.simulateRealtimeUsage();
    
    return 0;
}

JSON and FlatBuffers Interconversion

# monster.json - Sample JSON data
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

# Convert JSON to FlatBuffer binary format
flatc --binary monster.fbs monster.json

# Convert binary to JSON (for debugging/verification)
flatc --json monster.fbs monster.bin

# Check binary file size
ls -la monster.bin

# JSON vs FlatBuffer size comparison
echo "JSON size: $(wc -c < monster.json) bytes"
echo "FlatBuffer size: $(wc -c < monster.bin) bytes"

# Hex dump (binary structure inspection)
hexdump -C monster.bin | head -10

Advanced Schema Evolution and Best Practices

// version1.fbs - Initial version
namespace MyGame.Sample;

table Monster {
  hp:short = 100;
  mana:short = 150;
  name:string;
}

root_type Monster;
// version2.fbs - Safe evolution example
namespace MyGame.Sample;

table Monster {
  hp:short = 100;
  mana:short = 150;
  name:string;
  // Add new fields at the end (safe)
  level:short = 1;
  experience:int = 0;
  // Mark deprecated fields
  old_field:int (deprecated);
}

root_type Monster;
// version3.fbs - Safe reordering using IDs
namespace MyGame.Sample;

table Monster {
  // Use explicit IDs for safe reordering
  level:short = 1 (id: 3);
  hp:short = 100 (id: 0);
  experience:int = 0 (id: 4);
  mana:short = 150 (id: 1);
  name:string (id: 2);
  // New features
  skills:[string] (id: 5);
  metadata:[ubyte] (id: 6);
}

root_type Monster;
// Cross-version compatibility check
#include "flatbuffers/flatbuffers.h"

void checkCompatibility(const uint8_t* buffer) {
    auto monster = GetMonster(buffer);
    
    // Old fields are always accessible
    printf("HP: %d\n", monster->hp());
    printf("Name: %s\n", monster->name() ? monster->name()->c_str() : "null");
    
    // Check existence of new fields
    printf("Level: %d\n", monster->level());  // Default value returned
    
    // Check field existence by comparing with default value
    if (monster->level() != 1) {  // Different from default value
        printf("Level field was explicitly set to: %d\n", monster->level());
    }
    
    // Safe access to collection fields
    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");
    }
}