为何串行比并行更快?Rust单词计数代码性能异常排查
单词统计多线程版本比串行慢的问题分析
我编写了统计文章单词出现频率的代码,实现了串行、多线程及带线程池的多线程三种版本。测试后发现串行方法速度最快,测试结果为:串行7.93ms,多线程15.78ms,线程池25.33ms。文章拆分操作已在计时前完成,现咨询代码是否存在问题。
以下是串行与多线程版本的代码(不含线程池版本):
use std::collections::HashMap; use std::sync::{Arc, Mutex}; use std::thread; use std::time::SystemTime; pub fn word_count(article: &str) -> HashMap<String, i64> { let now1 = SystemTime::now(); let mut map = HashMap::new(); for word in article.split_whitespace() { let count = map.entry(word.to_string()).or_insert(0); *count += 1; } let after1 = SystemTime::now(); let d1 = after1.duration_since(now1); println!("single: {:?}", d1.as_ref().unwrap()); map } fn word_count_thread(word_vec: Vec<String>, counts: &Arc<Mutex<HashMap<String, i64>>>) { let mut p_count = HashMap::new(); for word in word_vec { *p_count.entry(word).or_insert(0) += 1; } let mut counts = counts.lock().unwrap(); for (word, count) in p_count { *counts.entry(word.to_string()).or_insert(0) += count; } } pub fn mt_word_count(article: &str) -> HashMap<String, i64> { let word_vec = article .split_whitespace() .map(|x| x.to_owned()) .collect::<Vec<String>>(); let count = Arc::new(Mutex::new(HashMap::new())); let len = word_vec.len(); let q1 = len / 4; let q2 = len / 2; let q3 = q1 * 3; let part1 = word_vec[..q1].to_vec(); let part2 = word_vec[q1..q2].to_vec(); let part3 = word_vec[q2..q3].to_vec(); let part4 = word_vec[q3..].to_vec(); let now2 = SystemTime::now(); let count1 = count.clone(); let count2 = count.clone(); let count3 = count.clone(); let count4 = count.clone(); let handle1 = thread::spawn(move || { word_count_thread(part1, &count1); }); let handle2 = thread::spawn(move || { word_count_thread(part2, &count2); }); let handle3 = thread::spawn(move || { word_count_thread(part3, &count3); }); let handle4 = thread::spawn(move || { word_count_thread(part4, &count4); }); handle1.join().unwrap(); handle2.join().unwrap(); handle3.join().unwrap(); handle4.join().unwrap(); let x = count.lock().unwrap().clone(); let after2 = SystemTime::now(); let d2 = after2.duration_since(now2); println!("muti: {:?}", d2.as_ref().unwrap()); x }
代码存在的问题及性能瓶颈分析
- 线程与同步开销过高:手动创建线程本身有启动开销,且合并结果时的
Mutex锁竞争会导致线程等待。虽然每个线程先本地统计再合并,但锁的竞争开销对于这个轻量任务来说,已经超过了并行计算带来的收益。 - 不必要的数据复制:拆分
word_vec时多次调用to_vec(),会产生大量内存复制开销;而串行版本直接遍历原字符串的拆分结果,无额外拷贝。 - Debug模式的影响:当前代码使用Debug模式编译,Rust在Debug下会关闭大部分优化,多线程的同步机制开销被放大,而串行HashMap的性能损失相对较小。
- 任务粒度不匹配:单词统计属于细粒度任务,当数据量未达到足够规模时,并行计算的优势无法抵消线程管理的额外开销。
优化建议
- 切换到Release模式测试:使用
cargo run --release编译运行,Release模式的优化会大幅降低多线程同步和数据处理的开销,可能让多线程版本性能反超串行。 - 减少数据复制:直接基于原字符串的切片范围分配任务,避免将
&str复制为String后再拆分,减少内存拷贝。 - 使用高效并行库:改用
rayon库的并行迭代器,它会自动管理线程池和任务拆分,比手动创建线程更高效;或使用dashmap这类无锁HashMap,避免锁竞争开销。 - 动态调整线程数:根据CPU核心数设置线程数量(可通过
num_cpus库获取),避免线程过多导致上下文切换开销。
内容的提问来源于stack exchange,提问作者jyz
相关产品推荐
相关产品推荐

