【问题标题】:Multithreaded performance (with Rust)多线程性能(使用 Rust)
【发布时间】:2021-04-28 11:02:02
【问题描述】:

我一直在运行以下实验来测试 Rust 中的多线程性能。

下面的代码基本上做了以下事情:

第 1 步:在主线程上生成 5000 万个随机 (key, value) 对。

第 2 步:通过将 5000 万对插入到 HashMap 中来处理它们。此处理步骤在count 线程上同时重复。每个线程都有自己的 HashMap。

use rand::Rng;
use std::sync::Arc;
use std::thread;
use std::collections::HashMap;
use std::time::Instant;

fn generate_values(count: usize) -> Vec<([u8; 3], u8)>{
  let mut generator = rand::thread_rng();
  let mut values = Vec::new();
  for _ in 0..count {
    let key = generator.gen::<[u8; 3]>();
    let value = generator.gen::<u8>();
    values.push((key, value));
  }
  values
}

fn process_values(values: &Arc<Vec<([u8; 3], u8)>>, count: usize) {
  let mut handles = Vec::new();
  for _ in 0..count {
    let values = Arc::clone(values);
    handles.push(thread::spawn(move || {
      let mut map = HashMap::new();
      for (key, value) in values.iter() {
        map.insert(key, value);
      }
    }));
  }
  for handle in handles {
    handle.join().unwrap();
  }
}

fn main() {
  let values = Arc::new(generate_values(50000000));
  
  println!("processing values...");
  for count in 1..=16 {
    let start = Instant::now();
    process_values(&values, count);
    println!("processing values, repeated over {} thread(s), took {:?}", count, start.elapsed());
  }
}

我在一台专用服务器上运行代码,该服务器配备一个 AMD Ryzen 7 3700X 8-Core 处理器和 64 GB RAM,运行 Ubuntu 20.04。服务器上没有其他任何东西在运行。

我原以为在 1 到 8 个线程上重复运行代码所花费的时间大致相同,但在 8 个线程上运行代码似乎比运行它多花费大约 23% 的时间(19.59 秒)超过 1 个线程(15.97 秒):

processing values, repeated over 1 thread(s), took 15.970677367s
processing values, repeated over 2 thread(s), took 15.936398062s
processing values, repeated over 3 thread(s), took 16.497970587s
processing values, repeated over 4 thread(s), took 17.233953355s
processing values, repeated over 5 thread(s), took 17.233057743s
processing values, repeated over 6 thread(s), took 18.223844841s
processing values, repeated over 7 thread(s), took 19.094954912s
processing values, repeated over 8 thread(s), took 19.592578442s
processing values, repeated over 9 thread(s), took 21.152438731s
processing values, repeated over 10 thread(s), took 22.881476672s
processing values, repeated over 11 thread(s), took 22.97713133s
processing values, repeated over 12 thread(s), took 23.841287249s
processing values, repeated over 13 thread(s), took 24.713425745s
processing values, repeated over 14 thread(s), took 24.979827585s
processing values, repeated over 15 thread(s), took 25.78961309s
processing values, repeated over 16 thread(s), took 26.511473666s

然后我认为它与超线程有关,所以我禁用了同时多线程:

echo off > /sys/devices/system/cpu/smt/control

这些是没有超线程的结果:

processing values, repeated over 1 thread(s), took 15.906120824s
processing values, repeated over 2 thread(s), took 15.927443081s
processing values, repeated over 3 thread(s), took 16.701871709s
processing values, repeated over 4 thread(s), took 16.73429606s
processing values, repeated over 5 thread(s), took 17.785883476s
processing values, repeated over 6 thread(s), took 18.171144237s
processing values, repeated over 7 thread(s), took 18.871619003s
processing values, repeated over 8 thread(s), took 19.439770035s
processing values, repeated over 9 thread(s), took 22.937699259s
processing values, repeated over 10 thread(s), took 25.164055752s
processing values, repeated over 11 thread(s), took 29.44375459s
processing values, repeated over 12 thread(s), took 30.436276538s
processing values, repeated over 13 thread(s), took 33.775704733s
processing values, repeated over 14 thread(s), took 35.962573012s
processing values, repeated over 15 thread(s), took 38.04670196s
processing values, repeated over 16 thread(s), took 40.535251291s

当从 1 个线程变为 8 个线程时,性能仍会意外下降 22-23%。 尽管从 8 到 16 线程时的性能是一致的并且符合预期(在 16 线程上运行代码比在 8 线程上运行大约需要两倍的时间)。

这是在count 线程与 1 个线程上运行代码所花费的相对时间的小图表。

在 8 核处理器上运行时,与 1 个线程相比,在 8 个线程上重复代码时,预期性能会下降 22-23% 吗?

换句话说,性能下降的原因是什么?

代码在发布时使用“cargo run --release”运行。

【问题讨论】:

  • 您是否在运行优化构建?你使用什么编译器标志?
  • 我很好奇你为什么使用Arc - 你的程序是确定性的,所以我不明白你为什么需要引用计数。
  • 您需要向您的程序添加更多分析点 - process_values 中的一些地方可能不应该被测量,例如当您克隆 @每个线程的 987654334@ - 这可能是主要原因。
  • 阿姆达尔定律。线程之间可能会争用保持堆线程安全的锁。在 HashMap::new(次要)和 map.insert 中。随着更多项目的添加,这需要增加哈希图。您强烈希望预先设置 容量,以便只占用一次堆锁。
  • 两种可能性:1) 内存和缓存是共享资源,当并行运行更多代码时,速度受限于该资源。您可以通过perf stat 获得一些线索,例如如果有更多的缓存未命中。 2)如果只有几个内核处于活动状态,现代处理器会以更高的频率运行。 Ryzen / 3700 X 在 Turbo 模式下,CPU 运行频率高达 4.4GHz,而基本时钟频率仅为 3.6GHz。

标签: multithreading rust


【解决方案1】:

两种可能:

  • 即使没有锁争用,内存和缓存也是共享资源,当并行运行更多代码时,速度会受到此资源的限制。您可以通过perf stat 获得一些线索,例如如果有更多的缓存未命中。
  • 如果只有几个内核处于活动状态,现代处理器会以更高的频率运行。 Ryzen / 3700 X 在睿频模式下,CPU 运行频率高达 4.4GHz,而基本时钟频率仅为 3.6GHz。

【讨论】:

    猜你喜欢
    • 2020-04-07
    • 1970-01-01
    • 2012-09-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多