外观
第 1 节:无谓 clone——复印是花钱的
症状
代码里到处是 .clone(),程序跑得慢、内存涨得快。尤其在大循环、热路径(每请求都执行的代码)里,clone 的账单很可观。
诊断
.clone() 是真的复印:分配新内存、逐字节复制(第 3 章的"复印机 vs 搬箱子"理论)。借引用(&)只复制一个地址,几乎免费。
实测(100,000 个字符串,release 模式):
实测数据
text
clone 版:6.89 ms
借用版:0.16 ms
clone 慢了 43.9 倍43 倍——这就是"每次路过都复印一份"的代价。字符串越长、循环越大,差距越夸张。
解药
解药一:能借用就别复印——只需要"看"的地方,传 &:
rust
// 慢:每轮复印整串
for item in &items {
let copy = item.clone();
total_len += copy.len();
}
// 快:只借来看
for item in &items {
total_len += item.len();
}解药二:要改就 &mut,不要"复印再改":
rust
// 慢:复印一份再改,原版丢弃
let mut modified = item.clone();
modified.push('!');
// 快:直接改原版(你有权改的话)
item.push('!');解药三:确实要拥有,考虑 into_iter()——"顺手把原件带走",不复印:
rust
// 慢:clone 一份进新集合
let names: Vec<String> = pets.iter().map(|p| p.name.clone()).collect();
// 快(如果 pets 之后不用了):原件直接搬
let names: Vec<String> = pets.into_iter().map(|p| p.name).collect();预防
- 搜索
.clone():cargo clippy有clone_on_copy、redundant_clone等 lint,cargo clippy定期跑(编译错误篇雷区 1 提过) - 热路径三问:①真要拥有吗?②能借用吗?③能搬走吗?——clone 是"最后手段"
- 字符串/大结构体是重灾区:小数字的 clone 是免费的(Copy,第 3 章),别误会;花钱的是 String/Vec/自定义结构体
.to_string()/.to_owned()也是复印:从&str造 String 同样是分配——能借&str就别 to_string