You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

为何串行比并行更快?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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.01 13:31:01