只改一个函数,性能提升10倍?解读一个 Rust 性能 PR

Lobsters Hottest 新闻

摘要

一篇深度博文,通过基准测试复现和代码分析,解读 GreptimeDB 中让 Prometheus 读取转换速度提升 10 倍的 Rust 性能 PR。

<p><a href="https://lobste.rs/s/sv9hwi/just_one_function_10x_faster_reading_rust">评论</a></p>
查看原文
查看缓存全文

缓存时间: 2026/08/06 18:12

# 只改一个函数,就能快 10 倍?解读一个 Rust 性能优化 PR 源码:https://greptime.com/blogs/2026-07-31-prom-read-conversion-optimization GreptimeDB 可以作为 Prometheus 的 remote read 后端。在这条链路的最后一步,查询引擎产生的列式 `RecordBatch` 会被转换成 Prometheus 协议中面向行的 `TimeSeries`:按标签集合对行分组,并将每个序列的样本收集到一起。 列式 RecordBatch 转换为面向行的 TimeSeries *图 1:查询引擎产生的列式 `RecordBatch`,被转换成 Prometheus 协议中面向行的 `TimeSeries`* 这个函数(`recordbatches_to_timeseries`)在仓库里长期无人改动,直到我们的 committer @lyang24 (https://github.com/lyang24)(我们去年采访过他 (https://greptime.com/blogs/2025-04-10-greptimedb-committer-interview-lyang24))提交了 PR #8587 (https://github.com/GreptimeTeam/greptimedb/pull/8587) 并重写了它。该 PR 恰好只修改了一个文件:`src/servers/src/prom_store.rs` (https://github.com/GreptimeTeam/greptimedb/blob/32a6fc09157dff97c32297a7ad0cb04c14d0884f/src/servers/src/prom_store.rs)。 > i️**注意:** GreptimeDB v1.2.0-beta.1 (https://greptime.com/blogs/2026-08-04-greptimedb-v1-2-0-beta-1-release) 已发布,并包含此优化。 我之所以读这段代码,是因为这个 PR 出现在眼前。PR 描述里包含作者画的一张图,右下角是一份 CPU profile:在 30 万条序列、4 个并发读的情况下,`RecordBatch`→`TimeSeries` 这一步占用了 36.2% 的 CPU,仅字典物化(dictionary materialization)就占了 8.9%。一个既不解压也不碰磁盘、只在内存里搬运数据的函数,却烧掉三分之一的 CPU——这个数字不正常。profile 来自作者,我没有复现那个环境。为了弄清这个改动本身到底有多大收益,我需要一个能跑起来的 benchmark,而 PR 并没有附带基准测试代码。于是我在公共入口上重写了一个,并在改动前后各跑了一遍。改动前的数据同样不好看:10K 行每次转换需要 3.7 ms,100K 行需要 44 ms,也就是每秒不到 300 万行。改动之后,最好的情况快了超过 10 倍。 这篇博文是对该 PR 的逐步解读,走的是和《比 Go 慢 5 倍?优化 Rust Protobuf 解码性能》(https://greptime.com/blogs/2024-04-09-rust-protobuf-performance) 相同的路线:先写 benchmark 复现,再逐段阅读代码。Benchmark 代码在这个 gist 里 (https://gist.github.com/killme2008/d279cbaf38aa195237419474a1686e24)。 ## 第 1 步:复现问题 首先,确定改动之前的状态。bench 通过 criterion (https://bheisler.github.io/criterion.rs/book/index.html) 直接驱动公共入口 `recordbatches_to_timeseries`: ```rust fn bench_prom_read_convert(c: &mut Criterion) { let mut group = c.benchmark_group("prom_read_convert"); group.measurement_time(Duration::from_secs(5)); // (series count, samples per series) let sizes = [(10, 1000), (100, 100), (1000, 100), (5000, 20)]; for encoding in [Encoding::Dictionary, Encoding::Utf8] { for ordering in [Ordering::Adjacent, Ordering::Interleaved] { for (series, samples) in sizes { let rows = series * samples; let (schema, batch) = build_recordbatch(series, samples, ordering, encoding); group.throughput(Throughput::Elements(rows as u64)); group.bench_with_input( BenchmarkId::new( format!("{}/{}", encoding.name(), ordering.name()), format!("{series}x{samples}"), ), &(schema, batch), |b, (schema, batch)| { b.iter(|| { let batches = RecordBatches::try_new(schema.clone(), vec![batch.clone()]) .unwrap(); black_box( recordbatches_to_timeseries("bench_metric", batches).unwrap(), ) }); }, ); } } } group.finish(); } ``` 循环遍历的三个维度都来自真实场景。encoding 方面,`Utf8` 是普通字符串列,`Dictionary` 是 PromQL 读路径实际返回的标签布局。ordering 方面,`adjacent` 表示一个序列的样本在结果中是

相似文章

Rust语言的性能

Lobsters Hottest

本次演讲分析了Rust相较于C++的性能优势与劣势,提供了基准测试和最佳实践。附有幻灯片和阅读材料。