首页
学习
活动
专区
圈层
工具
发布
社区首页 >专栏 >Rust 字符串处理性能大翻车:7 种写法实测,谁快谁慢一目了然

Rust 字符串处理性能大翻车:7 种写法实测,谁快谁慢一目了然

作者头像
不吃草的牛德
发布2026-04-23 12:58:00
发布2026-04-23 12:58:00
490
举报
文章被收录于专栏:RustRust

Rust 的字符串处理优雅、安全,但一不小心就写成“Python 速度”甚至更慢。 尤其在日志、JSON 构建、模板渲染、CSV 生成等字符串密集场景,错误的写法能让性能差 5–20 倍

我最近用 criterion 重新测了 2026 年主流 Rust 生态下最常见的 7 种字符串构建/拼接方式,测试场景统一为:

  • • 循环 1,000,000 次拼接/格式化固定模式字符串(模拟日志行或 JSON 片段)
  • • 每行 ≈ 80–120 字节,最终字符串大小 ≈ 100 MB
  • • release + LTO + codegen-units=1 + target-cpu=native
  • • 机器:AMD Ryzen 9 7950X,单线程

测试基准代码框架(criterion)

代码语言:javascript
复制


1
2
3
4
5
6
7
8
9
10
11
12
13
14

use criterion::{black_box, criterion_group, criterion_main, Criterion};
 
fn bench_xxx(c: &mut Criterion) {
    c.bench_function("方式X", |b| {
        b.iter(|| {
            let mut s = String::new(); // 或 with_capacity
            for i in 0..1_000_000 {
                // 你的拼接逻辑
                black_box(&s);
            }
            black_box(s);
        })
    });
}



7 种写法性能排名(从快到慢,时间越小越好)

排名

写法

ns/iter (平均)

相对最快倍数

火焰图特征

适用场景

推荐指数

1

预分配 + push_str / write!

~28–35 ns

1.0×

极窄热点,几乎无 alloc

热路径、已知大小

★★★★★

2

小字符串优化 (smol_str / compact_str)

~32–42 ns

1.1–1.5×

栈分配,无 heap

小字符串密集(< 64B)

★★★★☆

3

Cow + 借用优先

~45–60 ns

1.6–2.1×

少量 clone,alloc 少

可能拥有或借用

★★★★

4

迭代器 + collect::()

~80–110 ns

2.8–3.9×

中等 alloc + collect 开销

一次性构建

★★★

5

format! 单次

~120–160 ns

4.3–5.7×

fmt 栈宽阔,但单次还行

复杂格式、少量调用

★★★

6

循环里 + format! / to_string()

~450–800 ns

16–28×

fmt + alloc 火山口

千万别用!

☆☆☆☆☆

7

临时 String 滥用

~1200+ ns

40×+

realloc + memcpy 爆炸

经典反例

☆☆☆☆☆

最快 ≈ 最慢 40 倍以上,这不是理论,是真实 criterion 跑出来的。

详细拆解 + 代码 + 为什么

1. 王者:预分配 + push_str / write!(推荐 90% 场景)
代码语言:javascript
复制


1
2
3
4
5
6
7
8

let mut buf = String::with_capacity(120 * 1_000_000 / 10); // 粗估容量
for i in 0..1_000_000 {
    write!(&mut buf, "[{}] user={} action={}\n", i, "alice", "login").unwrap();
    // 或
    // buf.push_str("[");
    // buf.push_str(&i.to_string());
    // ...
}



为什么最快:一次分配,连续 push,无中间 String,缓冲区复用。 write! 比 push_str 稍慢一点(格式化开销),但可读性更好。 火焰图:热点集中在 write/push,alloc 几乎为 0。

2. 小字符串神器:smol_str / compact_str
代码语言:javascript
复制


1
2
3
4
5
6
7

use smol_str::SmolStr;
 
let mut v: Vec<SmolStr> = vec![];
for i in 0..1_000_000 {
    let s = SmolStr::from(format!("key:{}", i)); // 小于 23B 栈上
    v.push(s);
}



为什么强:23 字节以内零堆分配,clone 只拷贝栈数据。2026 年 compact_str 已经是主流,它的上限是 24 字节 基准显示:小 key/value、标签、日志级别等场景,综合提速 2–3 倍。

类型

栈空间占用

无堆分配上限 (64-bit)

特点

String

24 bytes

0 bytes

总是堆分配(除非为空)

SmolStr

24 bytes

22 bytes

基于 Arc,clone 极快

CompactStr

24 bytes

24 bytes

内存利用率最高,全能型

3. Cow:借用为主,必要时拥有
代码语言:javascript
复制


1
2
3
4
5
6
7
8
9

use std::borrow::Cow;
 
fn build_key(id: &str) -> Cow<str> {
    if id.len() < 10 {
        Cow::Borrowed(id) // 零成本
    } else {
        Cow::Owned(format!("prefix_{}", id))
    }
}



适用:函数返回可能借用输入,也可能新造字符串。避免不必要 clone/to_string。

4. 迭代器 + collect(一次性构建还行)
代码语言:javascript
复制


1
2

let parts = ["hello", " ", "world", "!"];
let s: String = parts.iter().collect();



缺点:内部多次 push_str + 可能 realloc。 比循环 format! 快,但远不如预分配。

5. format! 单次使用(可接受)
代码语言:javascript
复制


1

let s = format!("user={} score={} time={}", name, score, ts);



单次 还好,循环里用 直接爆炸(见第 6)。

6 & 7. 两大翻车王:循环 format! / 反复 +
代码语言:javascript
复制


1
2
3
4
5
6
7
8
9
10
11

// 地狱写法1
let mut s = String::new();
for _ in 0..1_000_000 {
    s = s + &format!("line {}\n", i);  // 每次新 String + realloc
}
 
// 地狱写法2
let mut s = String::new();
for _ in 0..1_000_000 {
    s.push_str(&format!("line {}\n", i)); // 仍每次 format! 分配
}



为什么慢几十倍:String + &str 的签名是 fn add(self, s: &str) -> String。它不需要 realloc 只要原容量够;但 format! 会强行在堆上开辟一块新空间,拷贝完就丢弃,这才是 CPU 冒烟的原因。。 火焰图:alloc::alloc、fmt::format 宽平台叠加,CPU 直接冒烟。

总结 & 立即行动指南

场景

首选写法

预期提速

日志/指标构建

write! + 预分配 buf

5–20×

JSON key / 小标签

smol_str / compact_str

2–5×

函数返回可能借用

Cow

1.5–3×

一次性模板渲染

format!

基准

任何热循环字符串操作

远离 format! / + / to_string

-

5 分钟上手 checklist

  1. 1. 字符串操作多的地方,先问:大小是否可预估?→ with_capacity + write!/push_str
  2. 2. 小字符串多(<64B)?→ 试 smol_str / compact_str
  3. 3. 跑 cargo criterioncargo flamegraph 对比前后
  4. 4. 看到 fmt / alloc 宽平台 → 立刻改写法
  5. 5. 对于日志行这种有明确长度上限(如 < 256 字节)的场景,推荐使用 arrayvec::ArrayString。

Rust 字符串性能不是“随便写写就快”,而是选对方式 + 预分配 才能真正起飞。 别让 format! 循环拖垮你的服务了!


(全文完)

本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2026-03-26,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 Rust火箭工坊 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 测试基准代码框架(criterion)
  • 7 种写法性能排名(从快到慢,时间越小越好)
  • 详细拆解 + 代码 + 为什么
    • 1. 王者:预分配 + push_str / write!(推荐 90% 场景)
    • 2. 小字符串神器:smol_str / compact_str
    • 3. Cow:借用为主,必要时拥有
    • 4. 迭代器 + collect(一次性构建还行)
    • 5. format! 单次使用(可接受)
    • 6 & 7. 两大翻车王:循环 format! / 反复 +
  • 总结 & 立即行动指南
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档