ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

mruby-benchmark 完全指南:在 mruby 嵌入式环境中测量执行时间与内存分配

mruby-benchmark 完全指南:在 mruby 嵌入式环境中测量执行时间与内存分配 编程语言语言运行时编译器解释器嵌入式【免费下载链接】mrubyLightweight Ruby项目地址https://gitcode.com/gh_mirrors/mr/mruby点击查看免费下载本文以 mruby 官方 mrbgem mruby-benchmark 为线索系统讲解如何在轻量级 Ruby 实现 mruby 中完成代码执行时间测量、多实现对比基准benchmark与内存分配统计。读者将掌握Benchmark.measure、Benchmark.realtime、Benchmark.bm三个核心入口的完整用法理解Benchmark::Tms与Benchmark::Report的数据结构并能基于仓库源码理解时间与内存指标的真实来源及其平台限制。概览为资源受限环境设计的轻量级基准工具mruby-benchmark是 mruby 的官方基准测试与性能剖析profilinggem提供简洁、轻量的基准能力用于测量 mruby 应用中的执行时间与内存使用情况。它专门面向嵌入式系统和资源受限环境embedded systems and resource-constrained environments设计API 与 CRuby 标准库的benchmark保持一致——按 mrbgem.rake 中的注释Benchmark.measure的构建方式与 CRuby 完全相同CPU 时间来自Process.times真实时间来自对单调时钟monotonic clock的Process.clock_gettime调用。整个 gem 的实现非常精简核心逻辑全部位于 mrblib/benchmark.rb约 130 行纯 Ruby 代码由Benchmark模块、Benchmark::Tms类和Benchmark::Report类三部分组成没有 C 扩展代码依赖关系也集中在 mruby-process、mruby-objectspace、mruby-sprintf 与 mruby-io 四个 gem 上。安装与依赖在 build_config.rb 中启用在 mruby 中gem 通过构建配置启用。编辑你的build_config.rb添加一行conf.gem :core mruby-benchmark需要注意mruby-benchmark并不在任何默认 gembox如 default.gembox、stdlib.gembox中必须手动加入。conf.gem :core ...表示使用 mruby 仓库内自带的核心 gem而不是从外部 gem 仓库拉取。传递依赖在 mrbgem.rake 中声明了四个依赖依赖 gem作用说明mruby-process提供时间测量原语Process.times与Process.clock_gettime(Process::CLOCK_MONOTONIC)均由它提供mruby-objectspace提供内存剖析内存统计基于ObjectSpace.count_objectsmruby-sprintf提供格式化Tms#to_s与Tms#format的格式化输出依赖%格式化mruby-io提供标准输出Report#report与Benchmark.bm通过$stdout打印结果其中mruby-process是时间测量的核心。根据 mruby-process 的 READMEProcess.clock_gettime、Process.times与Process::CLOCK_MONOTONIC均由该 gem 提供而它本身又依赖mruby-signal与mruby-structProcess::Tms是mruby-struct定义的Struct。mruby-process 的时间读取通过其平台端口port实现POSIX 端口在 ports/posix/clock_hal.c 中使用clock_gettime(2)缺失时回退到gettimeofday(2)读取墙钟并用getrusage(2)缺失时回退到times(2)换算读取 CPU 时间累计值Windows 端口在 ports/win/clock_hal.c 中则全部通过 Win32 API 调用实现。无 Float 构建的限制一个重要的构建前提是mruby-benchmark需要带 Float 支持的构建。因为Process.times返回浮点时间且Tms#to_s内部使用%f浮点格式化——在没有 Float 的构建MRB_NO_FLOAT中这两者都会直接抛异常。这一点在 README 的 Limitations 一节中明确列出。API 详解Benchmark 模块Benchmark模块是基准操作的唯一入口提供三个模块级方法。Benchmark.measure { block }→ Benchmark::Tms测量给定代码块的执行时间返回包含完整时间信息的Benchmark::Tms对象result Benchmark.measure do # code to benchmark 1000.times { string interpolation: #{42} } end puts result # Prints formatted timing information从 mrblib/benchmark.rb 的源码可以看到measure的实现思路在yield前后各取一次Process.times与Process.clock_gettime(Process::CLOCK_MONOTONIC)然后分别做差值。四个 CPU 时间分量utime、stime、cutime、cstime来自Process.times的差值real来自单调时钟读数的差值。Benchmark.realtime { block }→ Float只返回真实墙钟时间单位为秒的浮点数适合快速计时time Benchmark.realtime do sleep(0.1) end puts Took #{time} seconds # Took 0.100... seconds源码实现mrblib/benchmark.rb比measure更简单取两次Process.clock_gettime(Process::CLOCK_MONOTONIC)之差不关心 CPU 时间。Benchmark.bm(label_width 0) { |x| ... }执行带格式化的多项基准对比输出各标签对齐的结果Benchmark.bm(10) do |x| x.report(array:) { 1000.times { [1, 2, 3, 4, 5] } } x.report(hash:) { 1000.times { {a: 1, b: 2, c: 3} } } x.report(string:) { 1000.times { hello * 100 } } end输出示例user system total real array: 0.010000 0.000000 0.010000 ( 0.012345) hash: 0.015000 0.000000 0.015000 ( 0.016789) string: 0.008000 0.000000 0.008000 ( 0.009012)参数label_width指定标签列宽大于 0 时会在打印头部前输出对应宽度的空格保证标签列对齐。从源码mrblib/benchmark.rb可见bm会先打印 user system total real表头然后创建Benchmark::Report实例并yield给用户代码块最后返回该Report对象其中保留了所有Tms结果可通过report.results取回。Benchmark.measure(memory: true) { block }→ Benchmark::Tmsmeasure支持memory:关键字参数。传入memory: true时除了测量时间还会统计对象分配数量与估算内存占用result Benchmark.measure(memory: true) do array [] 1000.times { |i| array i } end puts Objects allocated: #{result.objects} puts Memory used: #{result.memory} bytes内存统计的实现细节mrblib/benchmark.rb在块执行前后各调用一次ObjectSpace.count_objects仅当ObjectSpace常量存在时分别对返回哈希的values求和得到存活对象总数objects_allocated end_objects - start_objects即差值memory_allocated objects_allocated * 40即按“mruby 平均对象开销约 40 字节”的近似估算。这里 40 字节是源码中的固定估算值注释明确写着 “Average object overhead in mruby (approximate)”。因此内存结果是估算值可能并不反映真实的堆使用情况。注意ObjectSpace.count_objects来自mruby-objectspacegem。其 C 实现在 src/mruby_objectspace.c会遍历整个 mruby 对象空间按类型:TOTAL、:FREE、:T_OBJECT、:T_CLASS、:T_STRING、:T_ARRAY等统计存活对象数量并返回一个 Hash。Benchmark::Tms 类Benchmark::Tms持有一次测量的结果。构造时接收(utime, stime, cutime, cstime, real, label nil, objects nil, memory nil)mrblib/benchmark.rb。属性Attributes属性含义类型utime用户态 CPU 时间秒Floatstime系统态 CPU 时间秒Floatcutime块内被回收reaped的子进程的用户态 CPU 时间Floatcstime块内被回收的子进程的系统态 CPU 时间Floatreal真实墙钟时间秒Floatobjects分配的对象数量启用内存跟踪时Integermemory分配的内存字节数启用内存跟踪时Integer方法total→ Float返回总 CPU 时间即utime stime cutime cstimemrblib/benchmark.rbresult Benchmark.measure { heavy_computation } puts Total CPU time: #{result.total} secondsto_s→ String返回格式化字符串表示格式为%10.6f %10.6f %10.6f (%10.6f)\n % [utime, stime, total, real]即依次是 utime、stime、total、real各占 10 位宽、6 位小数real 用括号包裹result Benchmark.measure { sleep(0.1) } puts result.to_s # 0.000000 0.000000 0.000000 ( 0.100123)format(format_str)→ String按格式字符串输出结果。格式化通过字符串替换实现mrblib/benchmark.rb支持以下说明符说明符含义%u用户态 CPU 时间%s系统态 CPU 时间%t总 CPU 时间%r真实时间%o分配的对象数启用内存跟踪时%m分配的内存启用内存跟踪时%n标签名labelresult Benchmark.measure { computation } puts result.format(Real: %rs, CPU: %ts) # Real: 0.123s, CPU: 0.100s从源码实现可以看出%o、%m、%n只在对应字段非空时才会被替换if objects、if memory、if label守卫未启用内存跟踪或未设置标签时这些说明符会原样保留。Benchmark::Report 类Benchmark::Report在Benchmark.bm内部使用负责格式化的逐项报告输出。report(label ) { block }在bm块内执行并报告单个基准项Benchmark.bm do |x| x.report(first test) { code1 } x.report(second test) { code2 } endreport的实现mrblib/benchmark.rb会调用Benchmark.measure { yield }测量把 label 写入Tms对象measure本身构造的Tms不带 labelreport会重建一个带 label 的副本按构造Report时传入的width对标签做右补空格对齐通过$stdout.print输出label_str加tms.to_s把Tms存入results数组并返回它。因此bm块内每次x.report(...)的返回值就是对应的Tms可以收集起来用于断言或后续分析Report#results则返回全部结果的数组。实战示例基础计时require benchmark # Simple timing time Benchmark.realtime do sum 0 1000000.times { |i| sum i } end puts Calculation took #{time} seconds # Detailed timing result Benchmark.measure do arr (1..10000).to_a arr.sort! end puts result对比不同实现require benchmark Benchmark.bm(15) do |x| x.report(Array#each:) do arr (1..1000).to_a sum 0 arr.each { |n| sum n } end x.report(Array#inject:) do arr (1..1000).to_a arr.inject(0) { |sum, n| sum n } end x.report(Numeric#times:) do sum 0 1000.times { |n| sum n } end end内存剖析require benchmark # Track memory allocation result Benchmark.measure(memory: true) do strings [] 1000.times { |i| strings string_#{i} } end puts Execution time: #{result.real}s puts Objects created: #{result.objects} puts Memory allocated: #{result.memory} bytes在测试中做性能断言# In test files assert(String concatenation performance) do time Benchmark.realtime do 1000.times { hello world } end # Assert it completes within reasonable time assert_true time 0.1, String concat should be fast end在 mruby 的测试框架mruby-test中assert/assert_true的语法与 CRuby 的 minitest 一致可以直接把Benchmark.realtime的返回值写进断言。测试断言需要谨慎在时间片粒度较大的平台上过小的耗时可能被时钟粒度吞掉导致时间读数恰好为 0。实现原理时间测量README 的 Implementation Notes 明确说明mruby-benchmark的测量方式与 CRuby 的benchmark完全一致真实时间 两次Process.clock_gettime(Process::CLOCK_MONOTONIC)读数之差。由于使用的是单调时钟monotonic clock只增不减、不受系统时间调整影响即使块执行期间 NTP 校准了墙钟也不会体现在测量结果中四个 CPU 时间utime、stime、cutime、cstime 两次Process.times读数之差。两者都来自mruby-process而mruby-process通过其平台端口读取。各平台能区分的内容可参考 mruby-process 的 READMEPOSIX 端口下Process.times优先用getrusage(2)读取微秒级粒度缺失时回退到times(2)按 tick 换算Windows 端口则使用 Win32 调用cutime/cstime恒为 0因为 Win32 不报告已回收子进程的 CPU 时间CRuby 的 Windows 构建同样如此。从 mruby-process 的源码注释include/process_hal.h还可以看到一层设计细节Process.times跨 HAL 边界时以四个int64_t秒/纳秒时钟读数传递从不以 tick 或 Float 形式传递到公共层才统一转换为 FloatProcess::CLOCK_MONOTONIC是 mruby 自己的常量值Linux 上是 1、macOS 上是 6而非直接使用平台值。实现原理内存跟踪内存剖析基于ObjectSpace.count_objectsmruby-objectspace提供来跟踪对象分配通过块前后对象总数之差得到分配数量再乘以固定的 40 字节平均对象开销估算内存占用。README 强调“内存大小估算基于典型对象开销可能并非所有平台都精确”。因此在实际使用中需要注意objects统计的是“当前存活对象的净增量”块内分配但块内释放的对象不会计入memory是objects * 40的线性估算不反映真实堆使用例如字符串内部缓冲、数组容量扩张等都不在估算范围内如果构建中没有mruby-objectspacemeasure(memory: true)会静默跳过内存统计源码中通过Object.const_defined?(:ObjectSpace)守卫objects与memory保持为nil。平台限制与注意事项README 的 Limitations 一节列出了以下关键限制限制说明cutime/cstime仅统计块内等待reaped的子进程与Process.times语义一致在 Windows 上恒为 0CPU 时间粒度取决于平台时钟getrusage(2)下为微秒级times(2)或 Win32 下为一个 tick真实时间需要宿主机支持CLOCK_MONOTONIC极少数不支持它的 POSIX 主机上measure和realtime会抛出Errno::EINVAL与 CRuby 的benchmark行为一致需要带 Float 的构建Process.times与Tms#to_s中的%f在无 Float 构建下都会抛异常内存测量为估算值可能无法反映真实堆使用情况GC 活动影响计时基准测量期间发生的 GC 可能影响时间结果第 3 条在 mruby-process 的 POSIX 端口中有对应实现佐证ports/posix/clock_hal.c 用MRB_PROCESS_HAVE_CLOCK_MONOTONIC宏探测宿主是否提供CLOCK_MONOTONIC缺失时clock_gettime调用失败并最终映射为Errno::EINVAL。官方测试用例佐证仓库自带的测试文件 test/benchmark.rb 覆盖了本 gem 的全部核心行为是理解各 API 语义的最佳参考Benchmark.measure返回Tms对象且各字段均为 Float无子进程时cutime/cstime为0.0构造耗时块后real 0验证墙钟时间确实在增长“CPU 时间计数”用例第 51-68 行用Process.times自检循环忙等直到 CPU 读数前进从而兼容 tick 粒度的times(2)平台——这段注释解释了为何基准断言要绕过时钟粒度问题“CPU 时间只计块内”用例第 70-87 行在测量前先消耗 CPU再测量近乎空的块验证 CPU 时间是两次读数之差而非累计值Tms#total用Tms.new(1.0, 2.0, 3.0, 4.0, 5.0).total 10.0验证求和逻辑Tms#format验证%u/%s/%t/%r以及带内存属性的%o/%m/%n替换内存跟踪用例用skip unless Object.const_defined?(:ObjectSpace)处理未启用mruby-objectspace的构建Benchmark.bm用例通过把$stdout置空来抑制输出并验证report.results的长度与Tms类型。延伸仓库自带的 benchmark 目录与rake benchmark除了mruby-benchmarkgem仓库根目录下还有一套独立的基准测试基础设施二者可以搭配使用理解它有助于你设计自己的基准方案benchmark/ 目录存放bm_*.rb脚本如 bm_fib.rb递归 Fibonacci、bm_ao_render.rb环境光遮蔽渲染含大量对象创建与浮点运算、bm_hash_access.rb对比字符串键与符号键的哈希查找等均是可独立运行的耗时型负载tasks/benchmark.rake 定义了rake benchmark任务用系统time -p对每个bm_*.rb脚本重复运行 4 次MRuby::Build::BENCHMARK_REPEAT 4取 usersys 的均值/最小值/最大值写入.dat文件再调用 gnuplot 依据 benchmark/plot.gpl 生成 PNG 柱状图该任务通过构建目标的benchmark_enabled?开关控制lib/mruby/build.rb且只对非 internal 目标生效。这套基础设施衡量的是“整个进程从启动到结束”的耗时粒度与mruby-benchmark的块内测量不同但目标一致为 mruby 的性能演进提供可复现的量化依据。对日常开发而言mruby-benchmarkgem 是嵌入到应用代码与测试中做定向测量的更合适选择。总结mruby-benchmark以约 130 行纯 Ruby 实现了与 CRubybenchmark语义对齐的三大能力measure完整时间信息 可选内存跟踪、realtime快速墙钟计时、bm格式化多项目对比。其时间测量完全建立在mruby-process的Process.times与Process.clock_gettime(CLOCK_MONOTONIC)之上内存测量建立在mruby-objectspace的ObjectSpace.count_objects之上。理解这些底层来源就能准确预判各平台上的粒度、限制与误差来源——这正是嵌入式与资源受限环境中做可靠性能测量的关键。赞分享编程语言语言运行时编译器解释器嵌入式【免费下载链接】mrubyLightweight Ruby项目地址https://gitcode.com/gh_mirrors/mr/mruby点击查看免费下载相关推荐H2O 内置 mruby-json 完全指南在 mruby 处理器中解析与生成 JSONH2O 内置 mruby json 完全指南在 mruby 处理器中解析与生成 JSON mruby json 是面向 mruby轻量级嵌入式 Ruby的后端网络8B参数大模型革命DeepSeek-R1-0528-Qwen3-8B如何重塑中小企业AI应用格局8B参数大模型革命DeepSeek R1 0528 Qwen3 8B如何重塑中小企业AI应用格局 导语 2025年大模型不再是大型企业的专属高端资源D编程语言语言运行时编译器解释器嵌入式mruby 编译与交叉编译完全指南从构建配置到嵌入集成nghttp2 内置 mruby 实战mruby 编译与交叉编译完全指南从构建配置到嵌入集成nghttp2 内置 mruby 实战 导读 mruby 是一套轻量级、可嵌入的 Ruby 实现其可观测性日志分析云原生流处理上一篇输入格式全解析如何用wav、numpy数组与JSONL清单驱动diar_streaming_sortformer_4spk-v2说话人分离下一篇用Clojure MCP创建专属AI智能体Agent配置系统实战教程创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进