Rerun Python SDK 日志管道基准测试实战:从吞吐量压测到微基准与火焰图剖析
【免费下载链接】rerunVisualize, query, and stream to train on multimodal robotics data.项目地址: https://gitcode.com/GitHub_Trending/re/rerun
导读
本文围绕 Rerun 仓库中 tests/python/log_benchmark/README.md 所定义的 Python SDK 日志(logging)管道基准测试展开,系统讲解如何通过pixi一键运行吞吐量(throughput)基准与单次调用开销(micro-benchmark),如何以py-spy生成火焰图定位热点,以及如何使用pytest-benchmark保存并对比两次改动前后的性能基线。读完本文,你将掌握 Rerun Python SDK 在本地进行性能回归检查与剖析的完整工作流,并能读懂测试用例背后的日志管道设计(行式rr.log、列式rr.send_columns、内存 sink 与 gRPC 直连等)。
这套基准测试是手动性能基准,不进入 CI,专门服务于本地 profiling 与回归检查——理解这一点,有助于你将它嵌入自己的开发流程,而不是等待 CI 结果。
基准测试的整体布局:三个文件各司其职
基准测试代码集中在tests/python/log_benchmark/目录下,由三个文件组成:
| 文件 | 职责 |
|---|---|
| tests/python/log_benchmark/init.py | 共享数据类Point3DInput、Transform3DInput,负责用固定随机种子生成可复现的输入数据 |
| tests/python/log_benchmark/test_log_benchmark.py | 吞吐量基准(throughput benchmarks):大数据批、逐点单条日志、图像、时间轴上的变换等 |
| tests/python/log_benchmark/test_micro_benchmark.py | 微基准(per-call overhead):剖析rr.log()管道中每个环节的固定开销 |
数据类的设计值得先看一眼,因为它决定了测试的可复现性:
Point3DInput.prepare(seed, num_points)使用np.random.default_rng(seed),以固定种子生成positions((num_points, 3)的 float32)、colors(uint32)和radii(float32)数组,并附带默认 label"some label"。Transform3DInput.prepare(seed, num_entities, num_time_steps)生成形状为(num_time_steps, num_entities, 3)的translations(取值[0, 10))和形状为(num_time_steps, num_entities, 3, 3)的mat3x3s(取值[-1, 1))。
也就是说,同一套基准在任意机器、任意时间运行,输入数据都完全一致,性能差异只可能来自代码改动与环境差异——这正是做回归对比的前提。
通过 pixi 一键运行全部基准
项目使用 pixi 管理任务。在仓库根目录(rerun/)下,直接运行:
# 运行全部基准: pixi run py-bench # 只运行吞吐量基准(排除名称含 "micro" 的用例): pixi run py-bench -k "not micro" # 只运行微基准: pixi run py-bench -k micro # 运行某个具体基准,例如 Points3D 的微基准: pixi run py-bench -k "micro_log-Points3D"-k是 pytest 的表达式过滤参数,这里py-bench命令会把-k之后的参数原样透传给 pytest。py-bench这个任务定义在 pixi.toml 中,其真实命令为:
py-bench = { cmd = "uvpy -m pytest --benchmark-only --benchmark-group-by=func --benchmark-sort=fullname", depends-on = ["py-build-release"] }从中可以读出三条关键信息:
- 依赖
py-build-release:运行基准前会先以 release 模式构建并安装 Python 绑定(maturin develop --release)。性能测试必须用 release 构建,否则 debug 构建的测量结果毫无意义。 --benchmark-only:只执行被pytest-benchmark插件识别的基准函数(即带benchmarkfixture 的测试),跳过普通断言测试。--benchmark-group-by=func --benchmark-sort=fullname:结果按函数分组、按完整名称排序,便于在终端输出中按用例横向对比。
独立运行单个基准(配合性能分析器)
吞吐量基准的test_log_benchmark.py在文件底部提供了if __name__ == "__main__"独立入口,用于脱离 pytest 直接运行,方便挂接 py-spy、perf 等分析器。先进入 pixi shell:
pixi shell然后直接运行:
# 独立运行 transform3d 吞吐量基准: uvpy -m tests.python.log_benchmark.test_log_benchmark transform3d # 带参数运行:10 个实体、10000 个时间步、按静态数据记录: uvpy -m tests.python.log_benchmark.test_log_benchmark transform3d --num-entities 10 --num-time-steps 10000 --static # 连接到一个正在运行的 Rerun viewer(先启动 `rerun`): uvpy -m tests.python.log_benchmark.test_log_benchmark transform3d --connect这个独立入口支持如下参数(定义见 test_log_benchmark.py):
| 参数 | 默认值 | 说明 |
|---|---|---|
benchmark(位置参数) | — | 目前仅支持transform3d |
--num-entities | 10 | 实体数量 |
--num-time-steps | 1000 | 时间步数量 |
--static | 关闭 | 以静态数据记录(不随时间变化) |
--connect | 关闭 | 通过 gRPC 连接正在运行的 Rerun viewer,而不是写入内存 sink |
--create-only | 关闭 | 只构造Transform3D实例、不实际记录,用于分离“对象构造”与“日志管道”的开销 |
注意--create-only这个选项的妙用:把log_transform3d_translation_mat3x3(构造 + 记录)与create_transform3d_translation_mat3x3(仅构造)分开计时,从而精确区分 SDK 对象构造开销与底层日志/编码/传输开销。这也是吞吐量测试test_bench_transform3d_translation_mat3x3与test_bench_create_transform3d_translation_mat3x3成对存在的原因。
运行时会打印实测吞吐量,例如:
Logged 100000 transforms in 3.21s (31153 transforms/second)(数字仅为示意,实际取决于硬件、构建配置与记录目标。)
用 py-spy 生成火焰图
在独立运行模式下,可以方便地挂接采样式分析器 py-spy(不会像 cProfile 那样显著干扰被测代码):
# 生成火焰图(Linux 上建议加 --native 以包含原生栈帧): sudo PYTHONPATH=rerun_py/rerun_sdk:rerun_py py-spy record -o flamegraph.svg -- \ .venv/bin/python -m tests.python.log_benchmark.test_log_benchmark transform3d要点说明:
PYTHONPATH=rerun_py/rerun_sdk:rerun_py让 Python 进程能导入仓库内尚未安装(或刚由py-build-release构建)的rerun包。--native会采样 Rust 侧的原生栈帧,由于 Rerun 的 Python 绑定是基于 PyO3/maturin 的 Rust 实现,定位热点通常必须依赖原生栈信息。- 生成的
flamegraph.svg用浏览器打开即可看到调用栈的横向火焰图,宽条即热点。Python 层与 Rust 原生层会分别体现,方便判断瓶颈在 SDK 封装、Arrow 编码还是底层存储/传输。
吞吐量基准详解:四种典型的日志形态
tests/python/log_benchmark/test_log_benchmark.py 中的吞吐量用例覆盖了四种典型的日志使用形态:
1. 单次调用写入超大批次(Points3D)
test_bench_points3d_large_batch固定参数num_points=50_000_000(5000 万点),一次性调用rr.log("large_batch", rr.Points3D(...)),测量单次调用承载超大数据量的能力,反映 Arrow 数组构建与传输的吞吐上限。
2. 海量小调用(逐点单条日志)
test_bench_points3d_many_individual固定num_points=100_000,在循环中逐点调用rr.log("single_point", rr.Points3D(...)),每次只带 1 个点。这是典型的“每帧逐实体记录”模式,测的是小消息场景下日志管道的消息处理速率。
3. 图像/Tensor 数据
test_bench_image固定参数为1024^2px-4channels-20000calls(1024×1024、4 通道、20000 次调用),循环记录同一张rr.Tensor(image)。注意它用np.zeros(...)构造全零图像,测量的是重复记录同尺寸张量的稳定吞吐。
4. 时间序列上的 Transform3D:行式 vs 列式
test_bench_transforms_over_time对同一批 10000 个变换,以num_transforms_per_batch为参数做了三档对比:1(纯行式)、100、1000(列式批大小)。
- 行式(batch=1):每个时间步调用
rr.set_time("frame", sequence=i)再rr.log(...),模拟逐帧逐实体记录; - 列式(batch>1):使用
rr.send_columns一次性提交一个时间列和多个组件列,例如:
rr.send_columns( "test_transform", indexes=[rr.TimeColumn("frame", sequence=times)], columns=rr.Transform3D.columns( translation=rand_trans[start:end], quaternion=rand_quats[start:end], scale=rand_scales[start:end], ), )rr.send_columns的语义在 rerun_py/rerun_sdk/rerun/_send_columns.py 中有明确说明:它与行式rr.log相对,接受多个等长列,每个索引处各列数据合并为一行逻辑记录;且该 API忽略通过rr.set_time设置的状态化时间,也不会自动注入log_tick/log_time默认时间轴。rr.TimeColumn要求sequence、duration、timestamp三选一且只能选一个(见 TimeColumn 实现),sequence对应整数序列(如帧号),duration对应相对时间,timestamp对应 Unix 绝对时间戳。这一组基准直接量化了“列式批量提交相对逐条set_time+log到底能快多少”,是选择日志策略的关键依据。
5. Transform3D + 双时间轴(吞吐与构造分离)
test_bench_transform3d_translation_mat3x3使用Transform3DInput(1000 实体 × 10000 时间步),对每条记录同时设置两个时间轴:frame(序列时间)与sim_time(time_index * 0.01的仿真时间);static=True参数变体则把整批数据作为静态数据记录(只记录一次,不随帧变化)。配合--static/--create-only独立入口,可以分别评估双时间轴场景下日志与对象构造各自的开销。
微基准详解:rr.log()管道的五层剖析
tests/python/log_benchmark/test_micro_benchmark.py 是理解 Python SDK 日志开销结构的最佳入口。它对四种 archetype(Scalars、Points3D、Transform3D、Boxes3D)分别测量日志管道中的五个阶段:
| 基准函数 | 测量环节 | 说明 |
|---|---|---|
test_bench_micro_construct | archetype 对象构造 | rr.Scalars(42.0)、rr.Points3D(...)等对象的纯 Python 构造开销 |
test_bench_micro_as_component_batches | 组件批次化 | 调用archetype.as_component_batches(),把 archetype 拆成组件批次 |
test_bench_micro_log_components | 组件批量记录 | 调用内部函数_log_components("test_entity", batches),进入日志管道的 Python 侧入口(rerun_py/rerun_sdk/rerun/_log.py) |
test_bench_micro_log_arrow_msg | Arrow 消息直达原生层 | 手动把组件批次转成 Arrow 数组后直接调用bindings.log_arrow_msg,测量绕过 archetype/批次化等 Python 封装后的原生层开销 |
test_bench_micro_log | 完整rr.log | 一次完整的rr.log("test_entity", archetype)调用(不包含对象构造,archetype 在基准外预先创建好) |
test_bench_micro_set_time | 时间轴设置 | 单独测量rr.set_time("frame", sequence=42)的开销 |
把同一 archetype 在这五个阶段的耗时横向对比,就能直观看出:开销究竟花在 Python 对象构造、组件序列化,还是原生 Rust 侧的编码/存储。例如某次测量中若log_arrow_msg与log差距明显,说明瓶颈在 Python 侧的封装层;若差距很小,则说明瓶颈已经下探到原生层,优化方向应转向 Rust 侧。
基准对比:保存基线、改动、再对比
性能回归检查的核心流程是“改动前后各跑一次,再对比”。pytest-benchmark提供了结果持久化能力:
# 在当前分支上保存一份基线: pixi run py-bench -k micro --benchmark-save=before # 修改代码、重新构建(py-bench 依赖 py-build-release,会自动重编),再保存一份: pixi run py-bench -k micro --benchmark-save=after保存的结果存放在仓库根目录下的.benchmarks/目录中,自动按序号命名,例如0001_before、0002_after。对比时使用pytest-benchmark自带的 CLI:
uv run pytest-benchmark compare 0001 0002compare会输出两份结果在均值、中位数、标准差、每轮耗时等维度上的逐用例对比。这里的uv run同样需要先处于 pixi 环境(pixi shell)中,因为pytest-benchmark是 Python 环境依赖。由于测试输入数据由固定随机种子生成(见__init__.py的prepare),两次运行的数据完全一致,因此对比结果可以归因于代码改动本身。
记录目标的选择:内存 sink 与 gRPC 直连的差异
基准代码中反复出现两个关键的 sink 选择,理解它们才能正确解读测量结果:
rr.memory_recording()(默认):将日志数据全部写入内存缓冲,而不是发送给 viewer。其实现见 rerun_py/rerun_sdk/rerun/_memory.py,本质是创建一个PyMemorySinkStorage,可后续通过MemoryRecording.num_msgs()或drain_as_bytes()读取(drain_as_bytes会先冲刷当前 sink)。吞吐量基准每个函数开头都调用rr.memory_recording()以重置一个新的空内存 sink——这一步至关重要,它确保多次测量互不污染,避免把上一轮数据混入本轮。内存 sink 排除了网络传输与 viewer 渲染的干扰,测的是“日志进入 SDK 并完成编码存储”这一段的纯性能。
--connect/rr.connect_grpc():通过 gRPC 连接一个正在运行的 Rerun viewer(rr.connect_grpc的实现见 rerun_py/rerun_sdk/rerun/sinks.py,它会立即返回、异步发送数据)。这种模式包含了序列化、传输与 viewer 侧接收的完整链路,更接近生产环境,但由于引入了网络与 viewer 侧负载,测量噪声更大,更适合做端到端验证而非精确基准。
小结:一条完整的本地性能工作流
把这套基准工具串起来,就得到了一条可复用的本地性能检查工作流:
- 跑全量或定向基准:
pixi run py-bench -k "not micro"(吞吐)或pixi run py-bench -k micro(微基准),先看整体是否有明显异常; - 定位热点:对疑似慢的用例,进入
pixi shell后用uvpy -m tests.python.log_benchmark.test_log_benchmark transform3d独立运行,配合py-spy record --native生成火焰图,区分 Python 层与 Rust 原生层瓶颈; - 隔离环节:利用
--create-only、微基准的五个阶段、以及行式/列式对比用例,把开销精确归因到对象构造、批次化、Arrow 编码、时间轴设置或原生 sink 等具体环节; - 回归对比:改动前
--benchmark-save=before,改动后--benchmark-save=after,用uv run pytest-benchmark compare 0001 0002出具定量结论。
这套基准并非 CI 中的自动门禁,而是为开发者准备的一套手动剖析工具箱。仓库内相关的文档入口是 tests/python/log_benchmark/README.md,测试数据类与用例代码分别在init.py、test_log_benchmark.py 与 test_micro_benchmark.py,建议在修改rr.log相关实现后,用上述流程跑一遍前后对比再合入改动。
【免费下载链接】rerunVisualize, query, and stream to train on multimodal robotics data.项目地址: https://gitcode.com/GitHub_Trending/re/rerun
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考