0

0

c++游戏开发中的数据序列化方案如何选择? (Protobuf vs FlatBuffers)

穿越時空

穿越時空

发布时间:2026-01-09 14:55:19

|

667人浏览过

|

来源于php中文网

原创

Protobuf在C++游戏中常被误用于每帧网络同步等实时场景,因其SerializeToString/ParseFromString默认堆分配+深拷贝,引发GC压力与缓存抖动;它适合配置、日志等一次性序列化场景。

c++游戏开发中的数据序列化方案如何选择? (protobuf vs flatbuffers)

Protobuf 在 C++ 游戏中为什么常被误用?

Protobuf 的 SerializeToString()ParseFromString() 默认走堆分配 + 深拷贝,对帧率敏感的实时逻辑(如网络同步、状态快照)会造成 GC 压力和缓存抖动。它适合配置文件、日志、编辑器工具链这类“一次序列化、长期复用”的场景,但不适合每帧都构造/解析的运行时数据。

  • 每次 ParseFromString() 都会 new 出新对象树,无法复用已有内存池
  • 字段访问需通过 getter,编译器难以内联,间接跳转开销明显
  • 不支持 zero-copy:必须完整解包才能读任意字段,无法只取 player.health

FlatBuffers 的 zero-copy 特性在游戏里怎么落地?

FlatBuffers 生成的二进制是内存映射友好的布局,直接把 buffer 指针传给逻辑层即可读写,无需解析步骤。这对网络模块尤其关键——收到 UDP 包后,GetRoot(data) 返回的是原 buffer 上的结构体引用,字段访问就是指针偏移计算。

  • 必须用 flatc --cpp 生成头文件,并确保 runtime(flatbuffers.h)版本与 schema 编译时一致
  • 写入需用 FlatBufferBuilder 构造,不能直接修改已生成的 buffer;动态字段(如背包物品列表)要用 vector 而非裸数组
  • 不支持默认值继承:所有可选字段必须显式设初值,否则读取时行为未定义
// 示例:从收到的字节流快速读取角色位置
const uint8_t* data = recv_buffer;
auto state = flatbuffers::GetRoot(data);
float x = state->position()->x(); // 直接内存访问,无函数调用

什么时候该回退到 hand-rolled 二进制协议?

当协议极简且高频(如每帧 60 次的输入压缩包),FlatBuffers 的 schema 解析开销和 padding 对齐反而成负担。此时手写 memcpy + 固定 offset 访问更可控。

  • 输入数据只有 uint16_t buttons + int8_t stick_x, stick_y,总长 4 字节 → 直接 reinterpret_cast(buf)
  • 需要跨平台字节序统一时,宁可用 htons()/ntohs() 显式转换,也不依赖 FlatBuffers 的 EndianSwap 冗余逻辑
  • 热更新要求字段增删不破坏旧客户端:hand-rolled 协议可预留 1~2 字节 flag 区,比 FlatBuffers 的 required/optional 更灵活

FlatBuffers 的 schema 设计陷阱

游戏数据常含嵌套动态结构(如技能效果链、AI 行为树节点),但 FlatBuffers 的 table 不支持递归引用,union 又强制单类型判别。强行套用会导致大量冗余字段或运行时类型检查。

Sider
Sider

多功能AI浏览器助手,帮助用户进行聊天、写作、阅读、翻译等

下载

立即学习C++免费学习笔记(深入)”;

  • 避免 table Effect { type: EffectType; damage: float; radius: float; duration: float; } —— 大部分字段对非伤害类效果是无效占位
  • 改用 union EffectUnion { DamageEffect, HealEffect, BuffEffect },但需在 C++ 侧手动 switch (effect->type()) 分发
  • 真正复杂逻辑建议拆出 Lua/ScriptVM 承载,FlatBuffers 只传 ID 和参数表,而非试图序列化行为本身

C++ 游戏里序列化不是选“更标准”的方案,而是看谁更愿意为帧率让步。FlatBuffers 的 zero-copy 是实打实的优势,但它的 schema 约束力会反向绑架你的运行时设计——这点比任何性能数字都难调试。

相关专题

更多
css中float用法
css中float用法

css中float属性允许元素脱离文档流并沿其父元素边缘排列,用于创建并排列、对齐文本图像、浮动菜单边栏和重叠元素。想了解更多float的相关内容,可以阅读本专题下面的文章。

556

2024.04.28

C++中int、float和double的区别
C++中int、float和double的区别

本专题整合了c++中int和double的区别,阅读专题下面的文章了解更多详细内容。

98

2025.10.23

switch语句用法
switch语句用法

switch语句用法:1、Switch语句只能用于整数类型,枚举类型和String类型,不能用于浮点数类型和布尔类型;2、每个case语句后面必须跟着一个break语句,以防止执行其他case的代码块,没有break语句,将会继续执行下一个case的代码块;3、可以在一个case语句中匹配多个值,使用逗号分隔;4、Switch语句中的default代码块是可选的等等。

528

2023.09.21

Java switch的用法
Java switch的用法

Java中的switch语句用于根据不同的条件执行不同的代码块。想了解更多switch的相关内容,可以阅读本专题下面的文章。

409

2024.03.13

c语言const用法
c语言const用法

const是关键字,可以用于声明常量、函数参数中的const修饰符、const修饰函数返回值、const修饰指针。详细介绍:1、声明常量,const关键字可用于声明常量,常量的值在程序运行期间不可修改,常量可以是基本数据类型,如整数、浮点数、字符等,也可是自定义的数据类型;2、函数参数中的const修饰符,const关键字可用于函数的参数中,表示该参数在函数内部不可修改等等。

520

2023.09.20

golang结构体相关大全
golang结构体相关大全

本专题整合了golang结构体相关大全,想了解更多内容,请阅读专题下面的文章。

194

2025.06.09

golang结构体方法
golang结构体方法

本专题整合了golang结构体相关内容,请阅读专题下面的文章了解更多。

186

2025.07.04

c语言union的用法
c语言union的用法

c语言union的用法是一种特殊的数据类型,它允许在相同的内存位置存储不同的数据类型,union的使用可以帮助我们节省内存空间,并且可以方便地在不同的数据类型之间进行转换。使用union时需要注意对应的成员是有效的,并且只能同时访问一个成员。本专题为大家提供union相关的文章、下载、课程内容,供大家免费下载体验。

122

2023.09.27

c++主流开发框架汇总
c++主流开发框架汇总

本专题整合了c++开发框架推荐,阅读专题下面的文章了解更多详细内容。

25

2026.01.09

热门下载

更多
网站特效
/
网站源码
/
网站素材
/
前端模板

精品课程

更多
相关推荐
/
热门推荐
/
最新课程
CSS3 教程
CSS3 教程

共18课时 | 4.4万人学习

Sass 教程
Sass 教程

共14课时 | 0.7万人学习

Pandas 教程
Pandas 教程

共15课时 | 0.9万人学习

关于我们 免责申明 举报中心 意见反馈 讲师合作 广告合作 最新更新
php中文网:公益在线php培训,帮助PHP学习者快速成长!
关注服务号 技术交流群
PHP中文网订阅号
每天精选资源文章推送

Copyright 2014-2026 https://www.php.cn/ All Rights Reserved | php.cn | 湘ICP备2023035733号