如何确保Box::new()执行堆内存分配?性能测试异常排查
Box::new()堆分配性能测试结果不符合预期的原因及优化禁用方法
优化发生的位置
你的测试结果远低于预期开销,是编译器优化和内存分配器缓存共同作用的结果:
Rust编译器的Release模式优化
若你使用cargo run --release运行代码,编译器会启用全量优化(默认opt-level=3):- 循环展开:将10万次循环拆分为更大批次执行,减少循环条件判断的额外开销,让累加和堆分配的实际执行占比更高。
- 小对象分配优化:
Box::new(42)分配的是固定大小的i32(4字节),编译器会针对这类小对象生成更高效的分配代码,甚至和分配器配合简化流程。
内存分配器的缓存机制
Rust默认使用的内存分配器(如jemalloc或新版本系统分配器)会维护小对象内存缓存池:
每次调用Box::new时,分配器无需向操作系统申请新内存页,直接从缓存池中取出空闲内存块,分配速度大幅提升。即便你用Box::leak泄漏内存,在缓存池耗尽前,分配器依然能快速提供内存块。
如何禁用这些优化
要看到堆分配的真实开销,可从以下两方面操作:
1. 禁用编译器优化
使用Debug模式编译运行代码(默认即为Debug模式,无需加--release参数),或在Cargo.toml中强制设置优化等级为0:
[profile.release] opt-level = 0
Debug模式下,编译器会关闭几乎所有优化:循环不会展开,每个迭代都执行完整流程,Box::new的代码保留最原始的调用路径,能真实反映单步堆分配的开销。
2. 绕过分配器缓存
若想彻底绕过分配器缓存,直接测试向操作系统申请内存的开销,可手动调用系统级内存分配函数(通过libc库调用mmap):
use std::time::Instant; use libc::{mmap, PROT_READ, PROT_WRITE, MAP_ANONYMOUS, MAP_PRIVATE}; fn main() { let start = Instant::now(); let mut sum = 0; for _ in 0..100000 { sum += 42; } println!("Simple sum: {:?}", start.elapsed()); let start2 = Instant::now(); for _ in 0..100000 { // 直接调用mmap申请4字节内存,绕过分配器缓存 let ptr = unsafe { mmap( std::ptr::null_mut(), 4, PROT_READ | PROT_WRITE, MAP_ANONYMOUS | MAP_PRIVATE, -1, 0, ) }; if ptr == libc::MAP_FAILED { panic!("mmap failed"); } } println!("Direct syscall alloc: {:?}", start2.elapsed()); }
这种方式每次都会向操作系统申请新的匿名内存块,能体现堆分配的真实系统开销,此时你会看到它与累加操作的差距远大于5倍。
内容的提问来源于stack exchange,提问作者yegor256
相关产品推荐
相关产品推荐

