十年匠心定制 · 商业建站与技术教学双线并行 咨询热线:400-886-1026 service@lmnt.cn
ARTICLE DETAIL

资讯详情

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

MNN 仓库中的 FlatBuffers Dart 使用指南:从 flatc 代码生成、二进制读取到 SDK 差异解析

MNN 仓库中的 FlatBuffers Dart 使用指南:从 flatc 代码生成、二进制读取到 SDK 差异解析 MNN 仓库中的 FlatBuffers Dart 使用指南从 flatc 代码生成、二进制读取到 SDK 差异解析【免费下载链接】MNNMNN: A blazing-fast, lightweight inference engine battle-tested by Alibaba, powering high-performance on-device LLMs and Edge AI.项目地址: https://gitcode.com/GitHub_Trending/mn/MNN导读本文以 MNN 仓库内嵌的 FlatBuffers 源码3rd_party/flatbuffers中的官方 Dart 使用文档为主体系统讲解如何在 Dart / Flutter 项目中通过flatc --dart生成类型安全的数据访问代码、读取二进制 FlatBuffer并完整梳理 Dart 实现与 Dart SDK 前端自带flat_buffers的五项关键差异。读完本文你将掌握从 Schema 到 Dart 生成代码的完整链路、读写 FlatBuffer 的标准姿势以及本实现中枚举、struct、64 位整数与 ObjectBuilder 等特性的正确用法与取舍依据。阅读前的知识铺垫在深入 Dart 特性之前官方文档建议先建立以下基础均可在本仓库中找到对应资料通用教程所有受支持语言含 Dart的完整 FlatBuffers 使用流程见 Tutorial构建 flatc编译生成 Schema 编译器flatc的方法见 BuildingSchema 编译器用法flatc各命令行选项的含义见 CompilerSchema 编写规范FlatBuffers 数据描述语言IDL的语法见 Schemas。DartUsage 文档本身定位为针对 Dart 的专项说明页因此它不会重复通用内容而是聚焦 Dart 特有的行为与坑点。FlatBuffers Dart 库代码位置官方实现中FlatBuffers 的 Dart 运行时库源码位于flatbuffers/dart目录。需要说明的是当前 MNN 仓库的3rd_party/flatbuffers副本以 C 与代码生成相关文件为主Dart 运行时库源码目录并不在此副本内但 Dart 相关的可验证产物确实保留在本仓库中主要包括tests/monster_test_my_game_generated.dart由flatc --dart从monster_test.fbs生成的最外层 Dart 代码tests/monster_test_my_game.example_generated.dart生成的嵌套命名空间代码枚举、struct、table、ObjectBuildertests/DartTest.sh官方提供的 Dart 测试执行脚本。这些生成文件正是阅读 Dart 生成代码结构的一手素材下文会反复引用。测试 FlatBuffers Dart 库官方测试代码位于flatbuffers/tests并通过 DartTest.sh 一键运行。脚本内容揭示了完整的测试流程其执行前提是已安装 Dart SDK脚本会先校验pub与dart是否在 PATH 中缺失则中止并提示。脚本的核心步骤可拆解为四步生成 Dart 代码调用本地构建的编译器将测试 Schema 转换为 Dart 源文件../flatc --dart -I include_test -o ../dart/test monster_test.fbs其中--dart指定输出 Dart 语言见 Compiler.md 中的--dart: Generate Dart code.-I include_test为 include 语句提供搜索路径-o ../dart/test指定输出目录拷贝二进制测试数据将monsterdata_test.mon一个预先生成的 FlatBuffer 二进制文件复制到../dart/test供测试程序读取解析依赖并运行进入dart目录执行pub get拉取package:flat_buffers运行时依赖然后运行dart test/flat_buffers_test.dart执行测试用例清理测试结束后删除拷贝的.mon文件。从脚本可以看出Dart 测试依赖package:flat_buffers作为运行时库生成代码中import package:flat_buffers/flat_buffers.dart as fb;与之对应pub get负责将其解析到本地。在 Dart 中使用 FlatBuffers读取二进制数据FlatBuffers 在 Dart 中同时支持读取与写入二进制 FlatBuffer。使用流程分两步用flatc的--dart选项从 Schema 生成 Dart 类生成代码默认命名为schema 名_generated.dart实际文件名受namespace影响例如测试中monster_test_my_game.example_generated.dart在业务代码中同时引入运行时库package:flat_buffers/flat_buffers.dart与生成的代码即可读取或构造 FlatBuffer。官方文档给出的读取示例以Monster表为例import dart:io as io; import package:flat_buffers/flat_buffers.dart as fb; import ./monster_my_game.sample_generated.dart as myGame; Listint data await new io.File(monster.dat).readAsBytes(); var monster new myGame.Monster(data);随后即可像访问普通 Dart 对象一样取值var hp monster.hp; var pos monster.pos;生成代码的读取链路剖析从本仓库保留的生成代码 monster_test_my_game.example_generated.dart 可以看到Monster读取端的真实结构class Monster { factory Monster(Listint bytes) { fb.BufferContext rootRef new fb.BufferContext.fromBytes(bytes); return reader.read(rootRef, 0); } static const fb.ReaderMonster reader const _MonsterReader(); final fb.BufferContext _bc; final int _bcOffset; int get hp const fb.Int16Reader().vTableGet(_bc, _bcOffset, 8, 100); }这里有几个值得注意的实现细节构造函数接受ListintUint8List是其子类型内部通过fb.BufferContext.fromBytes建立只读的内存上下文再由reader.read(rootRef, 0)在偏移 0 处根表位置解析出对象标量字段的访问走vtable 机制vTableGet(_bc, _bcOffset, 8, 100)中的8是字段在 vtable 中的槽位偏移100是字段默认值。当二进制中没有该字段时返回默认值这正是 FlatBuffers 前向/后向兼容的根基static const fb.ReaderMonster reader是复用型的读取器单例配合BufferContext做到零拷贝、零反序列化开销地按需读取字段。写入端ObjectBuilder 与 Builder从生成代码的结构看Dart 生成器同时提供了两套写入 API对应下文与 SDK 实现差异第 5 点ObjectBuilder 类如MonsterObjectBuilder、TestObjectBuilder构造函数接收普通 Dart 值finish(fb.Builder)负责将对象写入 Builder并提供便捷方法toBytes([String fileIdentifier])直接得到Uint8List。此类 API 更贴近直觉、易于使用代价是会产生额外的对象引用分配底层 Builderfb.Builder提供startTable()、endTable()等与 C 等其他语言构建器风格一致的原始操作ObjectBuilder 内部正是通过这些原语实现的。写入的典型流程是new fb.Builder()→ 用 ObjectBuilder 的finish落盘各字段 → 调用builder.finish(offset)收尾 → 从builder.buffer或 ObjectBuilder 的toBytes()取得序列化结果。具体字段参数与顺序请以实际生成的类构造函数签名为准。与 Dart SDK 前端 flat_buffers 的五项差异本仓库中的实现借鉴了 Dart SDK 前端 analyzer 包内部使用的实现但做了多处重大调整。理解这些差异是避免从 SDK 场景迁移时踩坑的关键。1. 移除 packed boolean 列表null 按 false 处理不再支持打包packed布尔列表这一特性在其他语言实现中并非标准且互不兼容因此被移除null 语义与 JavaScript 对齐布尔列表中的 null 值将被视为false若应用确实需要打包单个标量字段官方文档明确表示这只能由应用层自行实现库不提供该能力。2. 用专用枚举类替代 Dart 原生 enumDart SDK 原生 enum 只有在枚举值始终从 1 开始索引时才可用而 FlatBuffers 并不保证这一点例如测试 Schema 中Color.Red 1, Green 2, Blue 8值并不连续。因此本实现采用专用枚举类来保证 FlatBuffers 枚举值到 Dart 的映射与其他平台一致。以生成代码为例class Color { final int value; const Color._(this.value); factory Color.fromValue(int value) { ... } static bool containsValue(int value) values.containsKey(value); static const Color Red const Color._(1); static const Color Green const Color._(2); static const Color Blue const Color._(8); static get values {1: Red, 2: Green, 8: Blue}; static const fb.ReaderColor reader const _ColorReader(); }从源码结构看每个枚举都生成fromValue工厂校验非法值并抛StateError、containsValue查询方法、具名常量、values映射表以及配套的Reader含底层标量类型与size。AnyTypeId等联合类型枚举还额外生成minValue/maxValue边界常量。3. 完整支持 struct 与 struct 向量SDK 前端实现不支持 FlatBuffer struct以及 struct 向量把所有类型都当作内建标量或 table 处理。本实现将 struct 按与其他非 Dart 实现兼容的方式处理并正确支持 struct 向量为此许多以low前缀命名的方法被重新定位服务于 struct 读写。以生成代码中的Teststruct 为例class Test { Test._(this._bc, this._bcOffset); static const fb.ReaderTest reader const _TestReader(); int get a const fb.Int16Reader().read(_bc, _bcOffset 0); int get b const fb.Int8Reader().read(_bc, _bcOffset 2); }注意 struct 的字段访问不再走 vtable而是直接按内存偏移读取_bcOffset 0、_bcOffset 2因为 struct 在 FlatBuffers 中是内联定长布局。配套的_TestReader extends fb.StructReaderTest通过size 4声明其固定字节长度这正是 struct 与 table走TableReader vtable在 Dart 端的本质区别。4. 64 位整数不再降级为 float64SDK 实现将int64/uint64当作float64处理会损失精度本实现保留完整的 64 位整数语义并额外增加了 16 位整数支持如上面Test.a使用的Int16Reader。官方文档同时给出一个务实提醒这种取舍可能与 JavaScript 存在兼容性问题——如果必须与 JS 互通可以改用 JavaScript 实现或自行实现一个把所有 64 位数当作 float 的定制实现。但对 Dart VM 与 Flutter 场景而言保持整数精度是更重要的目标。5. 双轨代码生成ObjectBuilder 与 Builder这是本实现相对 SDK 的最大增强前面写入端小节已从生成代码角度展开ObjectBuilder 生成与 SDK 类风格相近、易用的高层 API代价是更多引用分配Builder 生成更贴近 C 等其他语言习惯的低层构建 API。开发者可按场景选择原型与业务代码用 ObjectBuilder追求极致性能与最小分配时用底层 Builder。文本解析JSON / Schema现状与边界官方文档明确Dart 目前不支持直接解析文本格式包括 Schema 与 JSON 的文本解析这点与 C/Java 等语言的--json/--binary文本工具链不同。可行的替代路径是通过Dart Native Extensions调用 C 解析器关于文本解析的完整说明见 C 文档即 CppUsage 与 Compiler.md 中--json、--binary等数据文件选项需要注意该方案目前不适用于 Flutter受平台扩展机制限制因此纯 Flutter 项目中应默认数据以二进制形式随包分发/通过网络传输将 JSON→FlatBuffer 的转换放在服务端或构建期完成。在 MNN 项目中的实际关联MNN 本身正是 FlatBuffers 的重度使用者模型的网络结构定义、算子参数等均以 FlatBuffers 二进制形式存储仓库 schema/current 下的MNN.fbs、CaffeOp.fbs、TensorflowOp.fbs等 Schema 文件以及编译时由 flatc 生成的MNN_generated.h、Tensor_generated.h等头文件都是这一机制的直观证据。--dart生成器与本仓库中的 C/Java 等其他语言生成器同源于 Compiler.md 中列出的flatc命令行体系因此 Dart 端生成的代码与 MNN 使用的 C 端代码遵循完全相同的二进制布局规范——这意味着用 Dart 读取 MNN 产出的模型二进制在布局层面是可行的只是实际工程中通常由 C 端含 MNN 推理引擎直接消费模型文件Dart 端更多出现在需要解析/生成 FlatBuffer 数据的跨端工具链场景。小结围绕 DartUsage 文档本文完整覆盖了前置知识Tutorial / Building / Compiler / Schemas、Dart 库位置与测试方法DartTest.sh的四步流程、flatc --dart的生成与读取实战、生成代码的 vtable 与 struct 读取原理以及五项 SDK 差异packed 布尔、枚举类、struct 支持、64 位整数、ObjectBuilder 双轨 API和文本解析的能力边界。以本仓库 DartTest.sh 与 生成代码 为验证依据这些结论均可直接在仓库中复现与对照。【免费下载链接】MNNMNN: A blazing-fast, lightweight inference engine battle-tested by Alibaba, powering high-performance on-device LLMs and Edge AI.项目地址: https://gitcode.com/GitHub_Trending/mn/MNN创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表