当前位置: 首页 > news >正文

Rust 性能优化秘籍:write! 宏让字符串构建提速 75%

[!NOTE]

在 Rust 编程中,字符串操作是非常常见的需求。很多开发者习惯使用 format! 宏来构建格式化字符串,但你是否知道在某些场景下,使用 write! 宏可以显著提升性能?本文将分享一个真实案例,展示如何通过简单替换字符串构建方法,实现高达 75% 的性能提升。

问题背景

作者在开发一个用于自定义虚拟机的 GUI 调试器时,需要实现内存查看功能,将程序内存中的每个字节以十六进制格式在网格中显示:

  • 每行显示 16 个字节
  • 左侧显示行索引(十六进制)
  • 右侧显示内存中每个字节的十六进制值

实现这一功能的自然方式是构建两个多行字符串:一个用于左侧的行索引,一个用于右侧的十六进制字节值。

初始实现:使用 format! 宏

作者最初的实现使用了 format! 宏来创建每个字节的十六进制表示:

/// 生成给定内存缓冲区的十六进制内存视图
fn generate_memory_strings(memory: &[u8], mut range: Range<usize>) -> Option<(String, String)> {const BYTES_PER_MEMORY_ROW: usize = 16;// 验证请求的字节范围if memory.len() < range.end {range.end = memory.len();if range.start > range.end {returnNone;}}let mem_view = &memory[range.start..range.end];// 包含行索引的字符串,用换行符分隔letmut lines_str = String::new();// 包含实际内存中十六进制字节的字符串,每行用换行符分隔letmut mem_str = String::new();// 内存视图左侧显示的行索引letmut row_index: usize = range.start;// 构建字符串letmut mem_rows = mem_view.array_chunks::<BYTES_PER_MEMORY_ROW>();for full_row in mem_rows.by_ref() {for byte in &full_row[..BYTES_PER_MEMORY_ROW-1] {mem_str.push_str(format!("{:02X}", *byte).as_str());mem_str.push(' ');}mem_str.push_str(format!("{:02X}", full_row[BYTES_PER_MEMORY_ROW-1]).as_str());mem_str.push('\n');lines_str.push_str(format!("{:#X}\n", row_index).as_str());row_index += BYTES_PER_MEMORY_ROW;}let remainder_row = mem_rows.remainder();if !remainder_row.is_empty() {lines_str.push_str(format!("{:#X}\n", row_index).as_str());for byte in remainder_row {mem_str.push_str(format!("{:02X}", *byte).as_str());mem_str.push(' ');}}Some((lines_str,mem_str))
}

性能瓶颈分析

尽管这种方法能够正常工作,但作者很快注意到,生成大块内存的十六进制视图非常缓慢。即使预先分配两个字符串缓冲区以避免重新分配,性能提升也微不足道。

经过思考,作者发现主要瓶颈是 format! 宏导致的大量堆内存分配。每次调用 format! 时,Rust 都会在堆上分配一个新的 String。在大多数情况下这没问题,但当字符串内容复制到更大的缓冲区(本例中是 mem_strlines_str)后立即丢弃时,这种堆分配就完全没有必要了。

优化方案:使用 write! 宏

write! 宏与 format! 不同,它直接将格式化的字符串写入可写流中,无需中间分配。以下是使用 write! 代替 format! 生成内存视图字符串的优化函数:

fn generate_memory_strings(memory: &[u8], mut range: Range<usize>) -> Option<(String, String)> {if memory.len() < range.end {range.end = memory.len();if range.start > range.end {returnNone;}}let mem_view = &memory[range.start..range.end];letmut lines_str = String::new();letmut mem_str = String::new();letmut row_index: usize = range.start;letmut mem_rows = mem_view.array_chunks::<BYTES_PER_MEMORY_ROW>();for full_row in mem_rows.by_ref() {for byte in &full_row[..BYTES_PER_MEMORY_ROW-1] {write!(mem_str, "{:02X} ", *byte).unwrap();}writeln!(mem_str, "{:02X}", full_row[BYTES_PER_MEMORY_ROW-1]).unwrap();writeln!(lines_str, "{:#X}", row_index).unwrap();row_index += BYTES_PER_MEMORY_ROW;}let remainder_row = mem_rows.remainder();if !remainder_row.is_empty() {writeln!(lines_str, "{:#X}", row_index).unwrap();for byte in remainder_row {write!(mem_str, "{:02X} ", *byte).unwrap();}}Some((lines_str,mem_str))
}

性能对比

使用 Rust 的 test::Bencher 进行基准测试,作者发现通过直接将格式化字符串写入最终缓冲区(而非创建大量临时字符串)实现了惊人的 75% 性能提升!

关键差异分析

让我们对比两个版本的关键差异:

  1. format! 版本

    mem_str.push_str(format!("{:02X}", *byte).as_str());
    

    这里每次格式化都会:

    • 在堆上分配一个新的 String
    • 格式化字节写入该 String
    • 将该 String 内容复制到 mem_str
    • 丢弃临时字符串
  2. write! 版本

    write!(mem_str, "{:02X}", *byte).unwrap();
    

    这里直接:将格式化的字节写入最终目标 mem_str, 无需任何中间堆分配

当需要处理大量字节并执行大量格式化操作时,这种差异累积起来会产生显著影响。

实际应用场景

这种优化特别适用于以下场景:

  1. 处理大量数据(如内存查看器、十六进制编辑器)
  2. 生成大型报告或日志
  3. 执行高频字符串格式化操作
  4. 资源受限环境(如嵌入式系统)

扩展应用:其他可写目标

除了 Stringwrite! 宏还可以用于任何实现了 std::fmt::Write trait 的类型,包括:

  • 文件(通过 std::io::Write
  • 网络套接字
  • 缓冲区
  • 自定义输出流

总结

在 Rust 中构建字符串时,format!write! 是两种常用的方法,但它们在性能上可能有显著差异:

  1. format!:创建新的格式化字符串,适合需要独立字符串的场景
  2. write!:直接写入目标流,避免中间分配,性能更佳

当需要将多个格式化结果合并到同一个缓冲区时,write! 是明显更好的选择,可以提供高达 75% 的性能提升。这是一个简单但强大的优化技巧,几乎不需要重构代码就能实现显著的性能改进。

下次编写 Rust 代码时,在需要构建大量字符串的场景下,记得考虑使用 write! 代替 format!,这可能会为你的应用程序带来可观的性能提升。

参考文章

  1. write! vs format! when constructing Rust strings by Nicholas Obert, Apr 2025, Medium
http://www.sczhlp.com/news/1336.html

相关文章:

  • 基于文件对比的技术写作内容碎片统一与上下文还原方法论
  • Rust 编译优化指南:如何让你的代码更小更快?
  • Windows下CMake安装及环境变量配置
  • Rust 字节处理入门指南:掌握 Vec、Cow 和零拷贝技术
  • 408-OS之阻塞IO和非阻塞IO
  • Python中字符串前“b”,“r”,“u”,“f”的作用
  • (个人思考) 直接使用GE,不用Ability
  • goethereum-地址检查 - Charlie
  • js高级第三天
  • 无需重训练即可教语音识别器学习新词
  • llama.cpp编译过程中的cmake版本问题 - Luna
  • 如何高效使用Cursor AI编程助手提升开发效率 | 完整配置与使用指南
  • WPF MVVM 入门学习笔记:从零开始理解 CommunityToolkit 与 ObservableObject 详解
  • 为所有人提供TSC频率:更精准的性能分析与基准测试
  • 第二十四天
  • Js 内存管理和闭包
  • js高级第二天
  • 双向循环链表完整实现与详解
  • JAVA学习
  • CSS 线性渐变
  • VMware ESXi 8.0U3g 发布 - 领先的裸机 Hypervisor
  • 装机软件记录
  • day3_javascript1
  • day4_javascript2
  • 电化学
  • 亚马逊AutoML论文获最佳论文奖
  • 前端加密实现
  • MX galaxy Day16
  • 30天总结-第二十八天
  • 金华の第二场模拟赛