无法在Rust中重现虚假缓存行共享问题

mvl*_*bat 4 benchmarking rust cpu-cache false-sharing

我正在尝试重现处理器缓存效果库的示例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;
    }
}
Run Code Online (Sandbox Code Playgroud)

如果我们创建传递给这个函数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])
}
Run Code Online (Sandbox Code Playgroud)

用两组参数(0,1,2,3和0,16,32,48)对它进行基准测试给出了几乎相同的结果:108.34和105.07微秒.

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

如果有人想看到我的完整基准测试代码,这里有链接: - lib.rs - cache_lines.rs

我想了解问题并找到重现类似结果的方法.

And*_*org 6

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

在更改之后,Rust会将1000000个添加项加3个加到3000000个中.您可以使用read_volatilewrite_volatile避免该优化.

虽然这两个变化足以证明您在我的测试中正在寻找的效果,但请注意,使用不安全的操作来改变不可变借的数组是未定义的行为.在允许unsafe代码支持某些不变量的情况下允许Rust进行优化,而这些代码不支持,因此Rust完全有权使用它感觉到的任何代码来替换代码.

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

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])
}
Run Code Online (Sandbox Code Playgroud)