使用 Swift Package Benchmark 对 FlatBuffers 与 FlexBuffers 进行基准测试:从运行命令到源码级解读
发布时间:2026/9/11 11:07:49
分类:文化教育
浏览:1234

使用 Swift Package Benchmark 对 FlatBuffers 与 FlexBuffers 进行基准测试从运行命令到源码级解读【免费下载链接】flatbuffersFlatBuffers: Memory Efficient Serialization Library项目地址: https://gitcode.com/GitHub_Trending/fl/flatbuffersFlatBuffers 是一个免解码、内存高效的序列化库其 Swift 版本同时提供 FlatBuffers 二进制格式与无模式schema-less的 FlexBuffers 两种编码。本文基于仓库 benchmarks/swift/README.md 展开完整介绍如何用package-benchmark这一 Swift 官方生态基准测试框架在 Xcode 与命令行两种环境下测量 FlatBuffers/FlexBuffers 在 Swift 中的读写性能并结合 FlatbuffersBenchmarks.swift 与 FlexBuffersBenchmarks.swift 两个基准测试源文件逐项解读每个基准用例的构建方式、度量指标与底层实现帮助你掌握如何设计、运行、解读 Swift 序列化基准测试的完整方法论。一、基准测试项目的整体结构整个 Swift 基准测试子项目位于仓库根目录下的benchmarks/swift/包含三个核心文件文件作用README.md运行入口文档只有两条命令Package.swiftSwiftPM 清单声明两个可执行 Target 与基准测试插件FlatbuffersBenchmarks.swiftFlatBuffers 二进制格式的读写基准FlexBuffersBenchmarks.swiftFlexBuffers 无模式格式的读写基准其中两个基准测试可执行文件分别针对 FlatBuffers 的FlatBufferBuilder与 FlexBuffers 的FlexBuffersWriter编写涵盖分配、清理、字符串、标量向量、结构体向量、表构建、解码读取等典型操作。二、如何运行 Swift 基准测试2.1 在 Xcode 中打开原文档给出的第一种方式是直接在 Xcode 中打开open --env BENCHMARK_DISABLE_JEMALLOCtrue Package.swift注意两点必须使用--env传入环境变量使BENCHMARK_DISABLE_JEMALLOC为true。package-benchmark在部分环境下会默认尝试注入 jemalloc 内存分配器以获取更精确的内存指标但并非所有运行环境都满足注入条件显式禁用可避免打开阶段报错。必须在benchmarks/swift/目录下执行该命令因为Package.swift中的依赖通过相对路径../..指向仓库根目录详见下文 Package 解读open会以当前目录解析清单。2.2 在终端直接运行第二种方式是命令行直跑swift package benchmark该命令会由BenchmarkPlugin来自package-benchmark驱动依次构建并运行FlatbuffersBenchmarks与FlexBuffersBenchmarks两个可执行 Target输出每个基准用例的度量结果。运行前需满足Swift 工具链版本 5.10 及以上见 Package.swift 第 1 行swift-tools-version:5.10运行平台为 macOS 13Ventura及以上Package.swift 第 22-24 行声明platforms: [.macOS(.v13)]首次运行需联网拉取package-benchmark1.27.0 依赖。此外package-benchmark还支持导出比较报告等高级用法例如swift package benchmark --format json可将结果导出为 JSON 以便在 CI 中归档与对比。三、Package.swift两个可执行 Target 与插件接线// swift-tools-version:5.10 let package Package( name: benchmarks, platforms: [.macOS(.v13)], dependencies: [ .package(path: ../..), .package(url: https://github.com/ordo-one/package-benchmark, from: 1.27.0), ], targets: [ .executableTarget( name: FlatbuffersBenchmarks, dependencies: [ .product(name: FlatBuffers, package: flatbuffers), .product(name: Benchmark, package: package-benchmark), ], path: Benchmarks/FlatbuffersBenchmarks, plugins: [.plugin(name: BenchmarkPlugin, package: package-benchmark)]), .executableTarget( name: FlexBuffersBenchmarks, dependencies: [ .product(name: FlexBuffers, package: flatbuffers), .product(name: Benchmark, package: package-benchmark), ], path: Benchmarks/FlexBuffersBenchmarks, plugins: [.plugin(name: BenchmarkPlugin, package: package-benchmark)]), ])这段清单揭示了几个关键设计本地依赖引用仓库根目录.package(path: ../..)指向仓库根 Package.swift。根清单中定义了FlatBuffers与FlexBuffers两个 library 产品分别位于 swift/Sources/FlatBuffers 与 swift/Sources/FlexBuffers基准测试通过.product(name:)直接消费这两个产品无需发布到任何注册中心。两个独立的可执行 Target将 FlatBuffers 与 FlexBuffers 的基准拆分为两个二进制避免互相干扰也便于单独运行其中一个。BenchmarkPlugin 插件每个 Target 都挂载了BenchmarkPlugin这是package-benchmark提供的 SwiftPM 插件swift package benchmark正是通过它完成构建、执行与结果汇总。四、FlatBuffers 基准测试源码解读FlatbuffersBenchmarks.swift 共定义了 12 个基准用例覆盖分配、写入与读取三类操作。4.1 统一的度量指标与运行配置所有用例共用同一组指标let metrics: [BenchmarkMetric] [ .cpuTotal, // CPU 总耗时 .wallClock, // 墙钟时间真实经过时间 .mallocCountTotal, // 总内存分配次数 .releaseCount, // 内存释放次数 .peakMemoryResident,// 峰值常驻内存 ]并基于Benchmark.Configuration构造三种配置let maxIterations 1_000_000 let maxDuration: Duration .seconds(3) // single缩放因子为 1用于大对象分配类用例 let singleConfiguration: Benchmark.Configuration .init( metrics: metrics, warmupIterations: 1, scalingFactor: .one, maxDuration: maxDuration, maxIterations: maxIterations) // kilo缩放因子 1000 let kiloConfiguration: Benchmark.Configuration .init( metrics: metrics, warmupIterations: 1, scalingFactor: .kilo, maxDuration: maxDuration, maxIterations: maxIterations) // mega缩放因子 1,000,000作为默认配置 let megaConfiguration: Benchmark.Configuration .init( metrics: metrics, warmupIterations: 1, scalingFactor: .mega, maxDuration: maxDuration, maxIterations: maxIterations) Benchmark.defaultConfiguration megaConfiguration参数含义scalingFactor迭代规模因子。benchmark.scaledIterations的规模由它决定超大因子适合微操作如单次create(string:)小因子适合重操作如分配 1GB 缓冲区warmupIterations预热迭代次数此处统一为 1预热结果不计入最终统计maxDuration单个用例最长运行 3 秒防止用例无限运行maxIterations迭代上限 100 万次与maxDuration互为双保险先到先止。未显式指定configuration的用例如 Strings 10自动使用Benchmark.defaultConfiguration即 mega 配置。4.2 预置测试数据struct AA: NativeStruct { public init(a: Double, b: Double) { self.a a; self.b b } var a: Double var b: Double } let oneGB: Int32 1_024_000_000 let data { ... }() // 两个 8888.88 组成的 Data let ints: [Int] Array(repeating: 42, count: 100) let bytes: [UInt8] Array(repeating: 42, count: 100) let str10 (0...9).map { _ - String in x }.joined() // 10 个 x let str100 (0...99).map { _ - String in x }.joined() // 100 个 x let array: [AA] [AA(a: 2.4, b: 2.4), ...] // 5 个 AAAA遵循NativeStruct协议定义于 swift/Sources/FlatBuffers/NativeObject.swift允许结构体按原生内存布局直接写入缓冲字符串特意构造为纯x串避免可变长度内容干扰定位到序列化本身的成本。4.3 分配与清理类基准Allocating 1GB单次配置下反复构造FlatBufferBuilder(initialSize: oneGB)。FlatBufferBuilder的指定初始化器签名见 FlatBufferBuilder.swift为public init(initialSize: Int32 1024, serializeDefaults force: Bool false)initialSize决定底层_InternalByteBuffer预分配字节数默认 1024 字节serializeDefaults控制是否将默认值也写入缓冲默认false只写非默认字段以节省空间。1GB 的用例用于衡量大缓冲初始分配的代价。Allocating ByteBuffer 1GB先通过UnsafeMutableRawPointer.allocate手工分配 1GB 内存再用ByteBuffer(assumingMemoryBound:capacity:)包装成ByteBuffer视图测量的是零拷贝包装而非拷贝分配的开销。注意测量区间用benchmark.startMeasurement()/benchmark.stopMeasurement()手动圈定将指针分配排除在计时之外。Clearing 1GB对一个持有 1GB 缓冲的 builder 反复调用fb.clear()。clear(keepingCapacity:)FlatBufferBuilder.swift会复位对齐状态、清空 vtable 缓存与内部字节缓冲keepingCapacity默认false表示释放容量此用例衡量缓冲复用时的清理成本。4.4 字符串与向量写入基准以下用例全部使用 mega 默认配置每个迭代内新建 builder 并测量Strings 10 / Strings 100分别写入 10 字符与 100 字符字符串。create(string:)FlatBufferBuilder.swift按 UTF-8 长度对齐后写入字节并以\0结尾返回Offset。blackHole用于消费返回值防止编译器优化掉无副作用的调用。Vector 1 Bytes / Vector 1 Ints单元素向量。createVector(_:)FlatBufferBuilder.swift将 Swift 数组压入缓冲并生成向量头。Vector 100 Ints / Vector 100 Bytes / Vector 100 ContiguousBytes100 元素向量。前两者走createVectorT: Scalar(_:)数组重载后者走createVector(bytes: any ContiguousBytes)FlatBufferBuilder.swift通过withUnsafeBytes零拷贝引用底层连续内存用于对比数组压栈与连续字节零拷贝两条路径。4.5 表构建基准FlatBufferBuilder Addkilo 配置模拟真实表写入流程——先create(string: T)得到字符串偏移startTable(with: 4)开启 4 字段的表FlatBufferBuilder.swift用add(element:def:at:)写入三个 Double 字段再用add(offset:at:)FlatBufferBuilder.swift写入字符串偏移最后endTable(at:)FlatBufferBuilder.swift完成 vtable 写入并返回表偏移。def:参数即默认值字段值与默认值相同时跳过写入。FlatBufferBuilder Start tablekilo 配置只做startTableendTable的空表隔离出 vtable 机制本身的固定开销作为 Add 用例的对照组。Struct单结构体写入。create(struct:)FlatBufferBuilder.swift按MemoryLayoutT.alignment对齐后按原生布局直接压入无 vtable。Structs5 个AA每个 16 字节组成的结构体向量写入经createVector(ofStructs:)FlatBufferBuilder.swift写入再包一层表并finish(offset:)FlatBufferBuilder.swift封口。此用例同时压测向量写入与整缓冲封口。Vector of Offsets三层嵌套结构——3 个字符串偏移组成的向量套两层createVector(ofOffsets:)FlatBufferBuilder.swift再写入 2 字段表。用于衡量偏移向量的间接寻址开销。4.6 读取基准Reading Doubles对 16 字节的Data包装成ByteBuffer在位置 0 反复read(def: Double.self, position: 0)隔离底层readT: BitwiseCopyable标量读取路径——这正是 FlatBuffers免解码直接读的核心能力。五、FlexBuffers 基准测试源码解读FlexBuffersBenchmarks.swift 聚焦无模式的 FlexBuffers 格式读写各占一半。所有用例使用 mega 默认配置同一组五项指标。5.1 代表性测试数据50 键 Map 字符串 100 元素向量let mapBuffer: ByteBuffer { var fbx FlexBuffersWriter(initialSize: 1 16) fbx.map { for i in 0..50 { $0.add(int: i, key: i\(i)) } $0.add(string: hello world, key: s) $0.vector(key: v) { v in for x in 0..100 { v.add(int: x) } } } fbx.finish() return fbx.sizedByteBuffer }()该 Map 包含 50 个键控标量、1 个键控字符串与 1 个包含 100 个标量的键控向量模拟真实业务负载供后续解码用例复用FlexBuffersWriter构建方式详见 swift/Sources/FlexBuffers/Writer/FlexBuffersWriter.swift。5.2 解码读路径基准Reading Doubles与 FlatBuffers 侧相同的ByteBuffer.read(def: Double.self, position: 0)原始标量读取作为两种格式读路径的公共基线。Decode Map ScalargetRoot(buffer: mapBuffer)!.map!解析根 MapgetRoot定义于 swift/Sources/FlexBuffers/Reader/Reference.swift当前实现仅支持小端系统再按键i25读取标量。Decode Map String同样解析根 Map 后按键读取字符串s衡量键查找与字符串视图构造成本。Decode Vector解析根 Map 后取出键v的向量遍历 100 个元素累加求和sum 使用溢出安全加法衡量向量遍历与逐元素类型分派成本。5.3 编码写路径基准写路径四个用例共享一个关键技巧Writer 复用。每个用例在测量前构造一次FlexBuffersWriter(initialSize: 1 20)每个迭代内完成写入、finish()、消费sizedByteBuffer后调用fbx.reset(keepingCapacity: true)FlexBuffersWriter.swift复位栈与字节缓冲但保留容量从而让单次迭代的分配不掩盖真实写路径成本。Strings 10 / Strings 100写入 10/100 字符字符串并封口。Encoding Vector Of Intsfbx.vector { $0.create(vector: ints) }写入 100 个Int32的向量。Encoding Map重建 50 键 Map 字符串 100 元素向量的完整结构衡量真实业务编码成本。源码注释特别说明写路径成本主要受exclusivity(unchecked)存储指针的影响复用 Writer 正是为了在基准中暴露这一底层实现的真实开销。六、结果解读与进一步探索6.1 关注哪些指标wallClock端到端真实耗时最直观的用户感知指标cpuTotalCPU 实际占用排除调度等待mallocCountTotal / releaseCount反映分配压力——FlatBuffers 的设计目标之一就是解码零分配mallocCountTotal越低越能体现该优势peakMemoryResident峰值内存用于评估大缓冲场景下的内存足迹。6.2 扩展思路修改Benchmark.Configuration的scalingFactor、maxDuration或maxIterations适配不同量级的工作负载在 FlatbuffersBenchmarks.swift 中仿照既有用例新增Benchmark(...) { }块即可将新场景纳入swift package benchmark的统一输出使用swift package benchmark --format json等命令导出结构化结果便于 CI 回归对比具体子命令可查阅package-benchmark文档。6.3 相关参考Swift 实现源码swift/Sources/FlatBuffers/FlatBufferBuilder.swiftBuilder 核心、swift/Sources/FlexBuffers/Writer/FlexBuffersWriter.swift 与 swift/Sources/FlexBuffers/Reader/Reference.swiftFlexBuffers 读写根清单 Package.swift定义FlatBuffers、FlexBuffers两个 library 产品及平台支持iOS 12 / macOS 10.14C 侧的横向对比基准记录见 docs/source/benchmarks.md其中包含 FlatBuffers 与 Protocol Buffers LITE、Rapid JSON、pugixml 等方案的 Decode/Encode/内存占用对比表可作为理解 Swift 基准定位的背景参考——Swift 侧基准更聚焦于本库内部各 API 路径的相对开销而非跨库对比。七、小结benchmarks/swift/下的基准测试项目虽然 README 只有两条命令但背后是一套完整的 Swift 性能度量工程通过package-benchmark插件统一管理构建与运行用可配置的Benchmark.Configuration指标、缩放因子、预热、时长、迭代上限精确控制测量边界以blackHole、startMeasurement/stopMeasurement、Writer 复用等手段隔离噪声。掌握 Package.swift、FlatbuffersBenchmarks.swift 与 FlexBuffersBenchmarks.swift 三个文件的协作方式后你既可以一键复现现成的基准结果也能快速扩展出针对自己数据模型的定制化性能测试。【免费下载链接】flatbuffersFlatBuffers: Memory Efficient Serialization Library项目地址: https://gitcode.com/GitHub_Trending/fl/flatbuffers创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考