为什么`Arc`和`Rc`的Rust性能会随着引用向量的大小而变化?

nue99wik  于 9个月前  发布在  其他
关注(0)|答案(1)|浏览(84)

在做关于智能指针的rustlings练习时,我想知道如果多个线程都在阅读这个列表,使用引用一个数字列表的Arc的性能成本是多少。
所以我创建了一个基于Arc练习的简单程序。它计算向量中的数字之和,首先在一个线程中使用Rc计算10次并取平均时间,然后在10个并行线程中使用Arc计算10次,并比较所用的时间:

use std::sync::Arc;
use std::rc::Rc;
use std::thread;
use std::time::Instant;

fn main() {
    let numbers: Vec<_> = (0..1_000_000_u128).collect();
    let shared_arc = Arc::new(numbers.clone());
    let shared_rc = Rc::new(numbers);

    let mut joinhandles = Vec::new();

    // Test Rc
    let start = Instant::now();

    for _ in 0..10 {
        (*shared_rc).iter().sum::<u128>();
    }

    let t1 = start.elapsed();
    println!("{:?}", t1 / 10);

    // Test Arc
    let t2 = Instant::now();

    for _ in 0..10 {
        let thread_numbers = Arc::clone(&shared_arc);

        joinhandles.push(thread::spawn(move || {
            (*thread_numbers).iter().sum::<u128>();
        }))
    }

    // Join all threads
    for handle in joinhandles {
        handle.join().unwrap();
    }

    let t3 = t2.elapsed();
    println!("{:?}",  t3);
}

我针对不同大小的numbers向量运行了这个程序,并始终发现运行时间如下:
| 尺寸numbers|时间Rc|时间Arc|
| --|--|--|
| 1 000 000 |4.7 ms| 8.5毫秒|
| 10 000 000 |47毫秒|99毫秒|
| 100 000 000 |497毫秒|1088毫秒|
使用Arc的线程通常比只使用Rc的线程花费两倍的时间!
据我所知,ArcRc的区别仅在于Arc以线程安全的方式更新其引用计数,而Rc则没有。然后,我预计Arc的性能成本会随着线程的数量(特别是上面代码中对Arc::clone的调用数量)而变化,但不会随着引用计数器引用的向量的大小而变化。
我想解决方案是以下两种之一:

  • 性能成本实际上是由于上述Arc测试的线程性质造成的;我不知道线程是如何工作的
  • (A)Rc-对象在每次线程解除引用时都会以某种方式被编辑。如果是,为什么?

但到底是哪一种呢?我很想听听你的想法!
编辑:
为了澄清,这是在没有编译器优化的情况下运行的。我的CPU是i7 8700 K(6核,12线程,12 MB缓存)。

4nkexdtk

4nkexdtk1#

您不是在比较ArcRc,而是在比较 * 并行 * 和 * 串行 * 执行。在第一个循环中,如果使用shared_arc而不是shared_rc,您可能不会看到性能上的任何差异。
此外,编译器无法生成您编写的和,因为它们的结果被丢弃。你应该用std::hint::black_box() Package 它们。你跟--release一起跑的,对吧?
在串行执行期间,16 MB阵列可能位于L3缓存中,并且由于遍历是直接的,因此预取可能对于L1/L2缓存是最佳的。然后这个计算全速运行。
根据您的硬件架构,特别是如果您的线程比硬件核心多,这些线程可能会为核心而“战斗”。这些上下文切换可能会降低性能,因为L1/L2缓存垃圾处理:一个线程的预取数据将在上下文切换之后从L1/L2高速缓存中逐出,以便在下一个上下文切换时加载新运行线程的相关数据,依此类推。当阵列较大时,L3缓存也会出现同样的问题。
一般来说,当一个问题是内存受限的(大部分时间都花在加载数据上,而不是实际计算上),并行化没有多大帮助(内存带宽不能更进一步,除非在NUMA系统上),甚至可能降低性能,由于缓存垃圾处理,如果太多的线程参与。此外,如果操作系统在同一个硬件核心上调度两个线程,它们之间的竞争将是坚韧的。根据我的经验,当我仔细地将线程绑定到不同的CPU内核时(假设系统中没有其他CPU密集型任务),我可以实现最佳的并行计算性能。
嘿,但我第一眼没发现:串行性能结果除以10,而并行性能结果不除以10。那么并行执行实际上比串行执行快得多。加速不是 * 理想的 *(N个线程×N),因为总是有一些开销(线程管理/调度,缓存和内存带宽的竞争......)。

**但是,最重要的是,这个问题显然是内存限制的(perf是这么说的),因此我们不能期望十个线程在相同的时间内获得十倍的数据。

相关问题