rust - 无法在 Rust 中重现错误的缓存行共享问题

标签 rust benchmarking cpu-cache false-sharing

我正在尝试重现 Gallery of Processor Cache Effects 的示例 6 .

文章以这个函数(在C#中)为例如何测试虚假共享:

private static int[] s_counter = new int[1024];
private void UpdateCounter(int position)
{
    for (int j = 0; j < 100000000; j++)
    {
        s_counter[position] = s_counter[position] + 3;
    }
}

如果我们创建线程将 0、1、2、3 个参数传递给此函数,计算将花费很长时间才能完成(作者得到 4.3 秒)。例如,如果我们通过 16、32、48、64,我们会得到更好的结果(0.28 秒)。

我在 Rust 中想出了以下函数:

pub fn cache_line_sharing(arr: [i32; 128], pos: usize) -> (i32, i32) {
    let arr = Arc::new(arr);
    let handles: Vec<_> = (0..4).map(|thread_number| {
        let arr = arr.clone();
        let pos = thread_number * pos;
        thread::spawn(move || unsafe {
            let p = (arr.as_ptr() as *mut i32).offset(pos as isize);
            for _ in 0..1_000_000 {
                *p = (*p).wrapping_add(3);
            }
        })
    }).collect();

    for handle in handles {
        handle.join().unwrap();
    }

    (arr[0], arr[1])
}

用两组参数(0、1、2、3 和 0、16、32、48)对其进行基准测试得到几乎相同的结果:108.34 和 105.07 微秒。

我使用标准 crate 作为基准测试。我有一台配备 Intel i5-5257U CPU (2.70GHz) 的 MacBook Pro 2015。我的系统报告有 64B 缓存行大小。

如果有人想查看我的完整基准测试代码,请访问以下链接: - lib.rs - cache_lines.rs

我想了解问题并找到重现与文章中类似结果的方法。

最佳答案

您的第一个问题是 *p.wrapping_add(3) 对指针而不是整数进行算术运算。循环的第一次迭代是在 p 之后加载三个空格的值,并将其存储在 p 中,Rust 将循环的其他 999999 次迭代优化为冗余。你的意思是 (*p).wrapping_add(3)

在那次更改之后,Rust 将 1000000 次加法 3 优化为 1 次加法 3000000。您可以使用 read_volatilewrite_volatile以避免这种优化。

虽然这两个更改足以证明您在我的测试中寻找的效果,但请注意,使用不安全操作来改变不可变借用的数组是 undefined behavior . Rust 被允许在 unsafe 代码支持某些不变量的假设下进行优化,而这段代码没有,所以 Rust 将完全是 within its rights用任何感觉的东西替换您的代码。

您可能使用不可变借用来绕过线程之间复制可变引用和可变指针的限制。我认为,这是一种绕过该限制的不太明确的方法(尽管老实说,如果有人回复指出这仍然是错误的,我不会太惊讶)。

pub fn cache_line_sharing(arr: [i32; 128], pos: usize) -> (i32, i32) {
    struct SyncWrapper(UnsafeCell<[i32; 128]>);
    unsafe impl Sync for SyncWrapper {}

    assert_ne!(pos, 0);
    let arr = Arc::new(SyncWrapper(UnsafeCell::new(arr)));
    let handles: Vec<_> = (0..4)
        .map(|thread_number| {
            let arr = arr.clone();
            let pos = thread_number * pos;
            thread::spawn(move || unsafe {
                let p: *mut i32 = &mut (*arr.0.get())[pos];
                for _ in 0..1_000_000 {
                    p.write_volatile(p.read_volatile().wrapping_add(3));
                }
            })
        })
        .collect();

    for handle in handles {
        handle.join().unwrap();
    }

    let arr = unsafe { *arr.0.get() };
    (arr[0], arr[1])
}

关于rust - 无法在 Rust 中重现错误的缓存行共享问题,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/54133092/

相关文章:

c - 缓存写入是否需要更长时间才能使更多缓存失效?

rust - 如何根据运行时信息有条件地跳过测试?

struct - 创建零大小结构的多种方法有什么区别?

java - 规范jvm : How to execute outside installation folder

security - 分支预测器 "train"是什么意思?

cpu-architecture - 访问内存时,是否会在缓存命中情况下设置页表访问/脏位?

macros - 使用宏,如何获得结构字段的唯一名称?

c - 将 libvips 静态链接到 Windows 中的 Rust 程序

用于测试性能的 C++ 单元测试(综合基准)

java - Java 基准测试(比较两个类)