ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

FlatBuffers C++ 性能基准解析:与 Protocol Buffers LITE、RapidJSON、pugixml 的量化对比

FlatBuffers C++ 性能基准解析:与 Protocol Buffers LITE、RapidJSON、pugixml 的量化对比 FlatBuffers C 性能基准解析与 Protocol Buffers LITE、RapidJSON、pugixml 的量化对比【免费下载链接】flatbuffersFlatBuffers: Memory Efficient Serialization Library项目地址: https://gitcode.com/GitHub_Trending/fl/flatbuffersFlatBuffers 是一个免解码zero-copy的内存高效序列化库其核心卖点在于访问数据不需要解析。本篇文章以官方基准文档 docs/source/benchmarks.md 为骨架逐项解读其在 Windows 7 64bit 环境下与 Protocol Buffers LITE、Rapid JSON、pugixml 以及裸结构体基线的对比数据并结合仓库中的 benchmarks 目录源码schema、基准实现与 CMake 构建脚本说明这些数据是怎么测出来的、为什么 FlatBuffers 在解码路径上能有数量级的优势以及这些结论在何种前提条件下成立。读完本文你将能够完整读懂官方基准表每一行指标的含义理解 FlatBuffers 解码零开销的底层机制知道如何在本仓库中找到并复现 C 与 Swift 两套基准代码并明确在选型时应如何把这份数据当作证据而不是营销话术。基准测试的背景与目的官方基准文档docs/source/benchmarks.md开宗明义这是一组C 环境的横向对比运行平台为 Windows 7 64bit。参与对比的序列化方案包括FlatBuffers二进制 wire format按设计意图使用也是本仓库的主打场景FlatBuffersJSON wire format使用可选的 JSON 解析器——先按 schema 把 JSON 解析进二进制 buffer之后访问方式与纯二进制完全一致Protocol Buffers LITE runtime文档明确说明选用 LITE 运行时因为其代码量更小、运行时开销更低即对 Protobuf 更有利的配置Rapid JSON当时公认最快的 C JSON 解析器之一pugixml当时最快的 XML 解析器之一Raw structs裸结构体不使用任何序列化库直接硬编码读写结构体的代码。其中 Raw structs 列是一个重要的参考系它是理论上最快、占用最小的实现但文档也毫不避讳地指出这种方式不跨平台、没有任何向前/向后兼容性。引入这一列是为了让读者看清与手写最优代码相比序列化库到底付出了多少代价。基准对象模拟游戏场景数据为了让对比有意义基准对象被设计成对游戏数据有代表性的结构例如一个场景scene格式。文档给出的描述是约 10 个对象包含一个数组、4 个字符串以及各种尺寸的 int/float 标量。这一设计在仓库源码中有完整的落点。基准 schema 定义在 benchmarks/cpp/flatbuffers/bench.fbs注释明确指出它试图代表典型的混合数据类型组合一个 3 元素的数组每个元素含 1 个字符串、3 个嵌套对象、9 个标量根元素持有该数组、一个额外字符串和一个枚举。具体结构如下namespace benchmarks_flatbuffers; enum Enum : short { Apples, Pears, Bananas} struct Foo { id:ulong; count:short; prefix:byte; length:uint; } struct Bar { parent:Foo; time:int; ratio:float; size:ushort; } table FooBar { sibling:Bar; name:string; rating:double; postfix:ubyte; } table FooBarContainer { list:[FooBar]; // 3 copies of the above initialized:bool; fruit:Enum; location:string; } root_type FooBarContainer;注意其中的类型组合覆盖了 8/16/32/64 位有符号与无符号整数、byte/ubyte、float/double、string、bool、enum 与 struct/table 嵌套——这正是为了公平检验各库对真实负载的处理能力。Foo、Bar是struct内联、固定布局FooBar、FooBarContainer是table可增删字段、带 vtable 偏移表这种 struct/table 混合布局本身就是 FlatBuffers 的一种典型用法。完整基准数据表逐行解读以下是官方文档中的完整数据表所有耗时均为执行 100 万次的秒数指标FlatBuffers (binary)Protocol Buffers LITERapid JSONFlatBuffers (JSON)pugixmlRaw structsDecode Traverse Dealloc1 百万次秒0.083025831051960.02Decode / Traverse / Dealloc分解0 / 0.08 / 0220 / 0.15 / 81294 / 0.9 / 28770 / 0.08 / 3541 / 3.9 / 1500 / 0.02 / 0Encode1 百万次秒3.21856501692730.15Wire format 大小普通 / zlib 压缩字节344 / 220228 / 1741475 / 3221029 / 2981137 / 341312 / 187存储解码后 wire 数据所需内存字节 / 块数0 / 0760 / 2065689 / 4328 / 134194 / 30 / 0解码期间瞬时分配内存KB011314340生成源码体积KB4610400手写遍历代码中的字段访问方式类型化访问器类型化访问器手动错误检查类型化访问器手动错误检查类型化但无安全检查库源码体积KB153800 的子集87433270逐行解读如下Decode Traverse Dealloc整体耗时FlatBuffers (binary) 仅 0.08 秒比 Raw structs0.02 秒慢 4 倍但比 Protobuf LITE302 秒快约3700 倍比 Rapid JSON583 秒快约7300 倍。即便使用 JSON wire formatFlatBuffers 仍然显著快于其他库105 秒 vs 196 秒的 pugixml、302 秒的 Protobuf LITE。Decode / Traverse / Dealloc分解这一行是全文最关键的证据。FlatBuffers (binary) 的三项分解是0 / 0.08 / 0——解码耗时为零、释放内存耗时为零只有真正遍历数据花了 0.08 秒。这正是零拷贝、零解析的直接体现读取时根本没有解码这个阶段。对比 Protobuf LITE 的 220 / 0.15 / 81其中 220 秒花在解码、81 秒花在释放解析出的临时对象上Rapid JSON 则是 294 / 0.9 / 287解码与释放几乎各占一半。Encode编码耗时FlatBuffers (binary) 为 3.2 秒虽然比 Raw structs0.15 秒慢约 21 倍但比 Protobuf LITE185 秒快约 58 倍比 Rapid JSON650 秒快约 200 倍。需要说明的是编码并非 FlatBuffers 最突出的优势其设计重心在读取路径但 3.2 秒对 100 万次编码而言仍属同级别方案中的领先水平。Wire format 大小这是唯一一项 FlatBuffers 不占优的指标。FlatBuffers (binary) 的 344 字节比 Protobuf LITE 的 228 字节大约 50%这符合两者的设计取向——Protobuf 采用高度紧凑的可变长整数编码而 FlatBuffers 为保证随机访问与对齐牺牲了一定的压缩率。zlib 压缩后两者差距缩小为 220 与 174 字节。JSON/XML 方案在此项上明显吃亏Rapid JSON 1475 字节、pugixml 1137 字节FlatBuffers (JSON) 为 1029 字节。存储解码后 wire 数据所需内存FlatBuffers (binary) 为0 字节 / 0 块——因为解码就是直接读取原 buffer不需要为解码结果单独开辟任何存储Raw structs 同理。而 Protobuf LITE 需要 760 字节 / 20 块Rapid JSON 高达 65689 字节 / 4 块pugixml 为 34194 字节 / 3 块。这一行直观解释了为什么 FlatBuffers 被称为 Memory Efficient不仅省时间还省内存。解码期间瞬时分配内存KBFlatBuffers (binary) 为 0因为它压根不分配Raw structs 为 0其余方案分别为 1Protobuf LITE、131Rapid JSON、34pugixml、4FlatBuffers JSON。零分配意味着没有 GC 压力、没有内存碎片这对游戏帧循环等场景意义重大。生成源码体积FlatBuffers 的生成代码仅 4 KBProtobuf LITE 为 61 KB约 15 倍。生成代码越小编译时间越短二进制体积越小对移动端与嵌入式环境更友好。字段访问方式FlatBuffers、Protobuf LITE 均提供类型化访问器如container-fruit()、list-Get(i)Rapid JSON 与 pugixml 需要手动错误检查而 Raw structs 虽然类型化但无任何安全检查。库源码体积FlatBuffers 运行时约 15 KB远小于 Protobuf3800 KB 的子集、pugixml327 KB与 Rapid JSON87 KB。前提与边界以上数据是官方文档记录的特定硬件/编译器环境下的实测值距今已有时日具体数字会随平台、编译器优化级别与库版本变化但解码零拷贝、零分配带来的数量级差距这一结构性结论是稳定成立的。引述时应说明这是文档记录的环境Windows 7 64bit不宜当作普适的绝对性能承诺。为什么 FlatBuffers 能解码零开销从源码层面看这一特性的实现机制在基准代码中暴露得很清楚。基准统一通过 benchmarks/cpp/bench.h 中的Bench接口抽象被测对象包含四个虚方法virtual uint8_t* Encode(void* buf, int64_t len) 0; virtual void* Decode(void* buf, int64_t len) 0; virtual int64_t Use(void* decoded) 0; virtual void Dealloc(void* decoded) 0;FlatBuffers 的实现位于 benchmarks/cpp/flatbuffers/fb_bench.cpp其Decode与Dealloc的实现是整个测试的题眼void* Decode(void* buffer, int64_t) override { return buffer; } void Dealloc(void*) override {};Decode只是把传入的 buffer 原样返回Dealloc什么都不做——因为 FlatBuffers 读取数据时直接以偏移量在原始字节流上定位字段根对象通过GetFooBarContainer(decoded)拿到后即可遍历。这从代码层面印证了表中0 / 0.08 / 0的分解数据。编码侧的实现同样值得注意Encode中先fbb.Clear()复用同一个 FlatBufferBuilder依次创建字符串、vector 与根 table最后fbb.Finish()。为了让基准尽可能逼近零分配基准头文件 benchmarks/cpp/flatbuffers/fb_bench.h 还提供了一个StaticAllocator直接使用调用方传入的栈上 buffer基准主程序中是uint8_t buffer[1024]作为构建器内存allocate原样返回该 buffer、deallocate为空操作struct StaticAllocator : public flatbuffers::Allocator { explicit StaticAllocator(uint8_t* buffer) : buffer_(buffer) {} uint8_t* allocate(size_t) override { return buffer_; } void deallocate(uint8_t*, size_t) override {} uint8_t* buffer_; };对比之下Raw structs 基线benchmarks/cpp/raw/raw_bench.cpp则用 placement new 直接在缓冲区上构造FooBarContainer连字符串都用定长char[32]数组内联存储注释明确说明这是为了避免strlen拖慢速度从而公平对比。这解释了 Raw structs 为何能快到 0.02 秒/0.15 秒——但代价正如文档所述不跨平台、无版本兼容。而主程序 benchmarks/cpp/benchmark_main.cpp 基于 Google Benchmark 与 gtest 组织BM_Flatbuffers_Encode/Decode/Use与BM_Raw_Encode/Decode/Use六个基准分别度量编码、解码、遍历三个阶段Use阶段还会用EXPECT_EQ(sum, 218812692406581874)校验遍历结果与预期校验和一致保证测的是正确的结果——遍历逻辑逐字段累加initialized、字符串长度、枚举、rating、ratio、size、time、count、id、length、prefix 等值与 schema 字段一一对应这也正是文档所说typing accessors访问方式的落地。如何构建与运行这套基准官方文档指出基准代码位于benchmarks/目录且位于 git 分支benchmarks中之所以单独放在分支里是因为它带有主项目不需要的 submodule 依赖且代码规范未达到主项目标准文档特别提示操作前先阅读benchmarks/cpp/README.txt该文件存在于benchmarks分支当前主仓库的benchmarks/目录中未包含它以当前仓库实际内容为准。在当前仓库中C 基准的构建入口是 benchmarks/CMakeLists.txt要求CMake 3.14通过FetchContent在配置期拉取googletestrelease-1.11.0与google/benchmarkv1.6.1两个依赖因此无需手动安装 submodule通过add_custom_command调用flatc从 benchmarks/cpp/flatbuffers/bench.fbs 生成 benchmarks/cpp/flatbuffers/bench_generated.h保证基准代码与 schema 始终同步生成可执行文件flatbenchmark要求 C11链接benchmark::benchmark_main使用 Google Benchmark 自带的入口与gtest用于在基准中做断言校验。从源码结构可以推断典型构建流程为在仓库根目录用 CMake 配置包含benchmarks子目录后直接构建并运行flatbenchmark目标即可得到各阶段耗时配合 Google Benchmark 的--benchmark_filter参数还可以只跑 FlatBuffers 或 Raw 的特定用例。另外仓库还提供了Swift 版本的基准位于 benchmarks/swift内含FlatbuffersBenchmarks与FlexBuffersBenchmarks两组用例、Package.swift 与说明文档 benchmarks/swift/README.md。这组 Swift 基准让同一套对比思路可以延伸到苹果生态也说明官方对性能的度量是持续维护的工程实践而非一次性数字。文档提及但未尚未基准化的序列化系统官方文档还列出了一些做过对比评估、但尚未跑基准的系统按适用性大致排序如下这些定性结论同样值得选型参考CapnProto承诺像 FlatBuffers 一样大幅改进 Protobuf 的体验但二进制编码更复杂、灵活性更差没有 optional 字段无法弃用字段、也无法序列化缺失但存在默认值的字段当时还不完全跨平台缺乏 VS 支持。msgpack与类型化 C 接口配合时向前/向后兼容支持非常有限且缺乏 VS2010 支持。Thrift与 Protobuf 非常相似但效率似乎更低依赖更多。YAMLJSON 的超集其余特性相似例如 Unity 使用它。C# 内置序列化Unity 也使用与语言强绑定且没有自动版本化支持限制了其适用性。Project AnarchyHavok 的免费移动引擎自带序列化系统但无自动版本化新字段需要手工处理、与引擎本身强耦合、且没有 schema 生成代码绑定在 C 类定义上。这些条目帮助读者理解 FlatBuffers 的定位它在接近手写裸结构体的性能与跨平台、schema 驱动、自动版本兼容之间取得了平衡而这一平衡正是上述系统各自没能同时满足的。结论这份基准教会我们什么从 docs/source/benchmarks.md 及其对应的 benchmarks 源码可以得到一组相互印证的结论读取路径是 FlatBuffers 的绝对强项解码零耗时、零分配0 / 0.08 / 0 的分解数据与Decode直接返回 buffer 的源码相互印证适合读多写少、数据被反复遍历的场景例如游戏资源加载、配置热更、服务端只读数据分发。编码路径与体积是取舍项编码 3.2 秒/100 万次仍远快于 Protobuf LITE但 wire 体积344 B大于 Protobuf228 B对带宽极度敏感的场景需要权衡必要时可用 zlib 压缩缓解220 B。Raw structs 是理论天花板FlatBuffers 相比它只慢了约 4 倍解码遍历与约 21 倍编码却换来了跨平台、schema 演进与前后兼容能力——这正是序列化库存在的意义。性能证据与实现细节在仓库中可复查schema 在 benchmarks/cpp/flatbuffers/bench.fbsFlatBuffers 侧实现与StaticAllocator在 fb_bench.cpp 与 fb_bench.h裸结构体基线在 raw_bench.cpp基准入口与校验逻辑在 benchmark_main.cpp构建方式见 benchmarks/CMakeLists.txt。最后提醒一点任何基准数据都有环境与版本边界。本文引用的数字来自官方文档记录的 Windows 7 64bit 环境其价值在于揭示机制层面的结构性差异零拷贝 vs 全量解析而非某个可复现的绝对秒数。在为自己的项目选型时最严谨的做法是复用本仓库benchmarks/的这套Bench抽象Encode/Decode/Use/Dealloc 四阶段 校验和断言在自己的目标平台与真实数据结构上重新跑一遍。【免费下载链接】flatbuffersFlatBuffers: Memory Efficient Serialization Library项目地址: https://gitcode.com/GitHub_Trending/fl/flatbuffers创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表