Warning: file_get_contents(/data/phpspider/zhask/data//catemap/0/performance/5.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
C++ 如何解决指针数组中的数据依赖关系?_C++_Performance_Compiler Optimization_Micro Optimization - Fatal编程技术网

C++ 如何解决指针数组中的数据依赖关系?

C++ 如何解决指针数组中的数据依赖关系?,c++,performance,compiler-optimization,micro-optimization,C++,Performance,Compiler Optimization,Micro Optimization,如果我们有一个整数指针数组,所有指针都指向同一个int,并在其上循环执行++操作,那么它将比那些指向两个不同int的指针慢100%。这里有一个具体的例子 int* data[2]; int a, b; a = b = 0; for (auto i = 0ul; i < 2; ++i) { // Case 3: 2.5 sec data[i] = &a; // Case 2: 1.25 sec // if (i & 1) //

如果我们有一个整数指针数组,所有指针都指向同一个int,并在其上循环执行
++
操作,那么它将比那些指向两个不同int的指针慢100%。这里有一个具体的例子

int* data[2];
int a, b;
a = b = 0;
for (auto i = 0ul; i < 2; ++i) {
    // Case 3: 2.5 sec
    data[i] = &a;

    // Case 2: 1.25 sec
    // if (i & 1)
    //     data[i] = &a;
    // else
    //     data[i] = &b;
}

for (auto i = 0ul; i < 1000000000; ++i) {
    // Case 1: 0.5sec
    // asm volatile("" : "+g"(i)); // deoptimize
    // ++*data[0];

    ++*data[i & 1];
}
int*数据[2];
INTA,b;
a=b=0;
用于(自动i=0ul;i<2;++i){
//案例3:2.5秒
数据[i]=&a;
//案例2:1.25秒
//如果(i&1)
//数据[i]=&a;
//否则
//数据[i]=&b;
}
用于(自动i=0ul;i<100000000;++i){
//案例1:0.5秒
//asm volatile(“:”+g“(i));//去优化
//++*数据[0];
++*数据[i&1];
}
总之,观察结果如下:(在环体中描述)

案例1(快速):++*指针[0]

案例2(中等):++*指针[i],半个指针指向一个int,另一半指向另一个int

案例3(慢速):++*指针[i],所有指针指向相同的int

以下是我目前的想法。案例1的速度很快,因为现代CPU知道我们正在读/写相同的内存位置,从而缓冲操作,而案例2和案例3则需要在每次迭代中写出结果。案例3比案例2慢的原因是,当我们通过指针a写入内存位置,然后尝试通过指针b读取时,我们必须等待写入完成。这将停止超标量执行

我理解正确吗?有没有办法在不改变指针数组的情况下使Case 3更快?(可能会添加一些CPU提示?)


这个问题是从实际问题中提取出来的

您已经发现了导致直方图中出现瓶颈的影响之一。解决这个问题的一个方法是保留多个计数器数组并在其中循环,这样相同索引的重复运行将分布在内存中的2或4个不同计数器上

(然后在计数器数组上循环,将它们相加为最后一组计数。这部分可以从SIMD中受益。)


案例1的速度很快,因为现代CPU知道我们正在读/写相同的内存位置,从而缓冲了操作

不,这不是CPU,而是编译时优化。

++*指针[0]
速度很快,因为编译器可以将存储/重新加载从循环中取出,实际上只是增加一个寄存器。(如果不使用结果,它甚至可能会优化掉该结果。)

假设没有数据竞争UB让编译器假设没有其他东西在修改指针[0],因此每次递增的对象肯定是同一个对象。“仿佛”规则允许它在寄存器中保留
*指针[0]
,而不是实际执行内存目标增量

这意味着增量有1个周期的延迟,当然,如果它完全展开并优化循环,它可以将多个增量合并为一个并执行
*指针[0]+=n


当我们通过指针a写入内存位置,然后尝试通过指针b读取它时,我们必须等待写入完成。这将停止超标量执行

是的,通过该内存位置的数据依赖性是问题所在。在编译时不知道所有指针都指向同一个位置的情况下,编译器将生成实际递增指向内存位置的asm

不过,“等待写入完成”并不是严格意义上的准确。CPU有一个存储缓冲区,用于将存储执行与缓存未命中分离,并将无序推测执行与实际提交到L1d并对其他内核可见的存储分离。重新加载最近存储的数据不必等待它提交到缓存存储转发一旦CPU检测到,从存储缓冲区到重新加载就是一件事

在现代Intel CPU上,存储转发延迟约为5个周期,因此内存目标添加具有6个周期的延迟。(1用于添加,5用于存储/重新加载(如果它位于关键路径上)

是的,无序执行允许这两个6周期延迟依赖链并行运行。循环开销隐藏在延迟之下,同样由OoO exec隐藏

相关的:

  • 在stuffedcow.net上
  • (在Sandybridge系列上,如果不立即尝试重新加载,则可以减少存储转发延迟。)

有没有办法在不改变指针数组的情况下使Case 3更快

是的,如果预计会发生这种情况,可能会对其进行分支

    int *current_pointer = pointer[0];
    int repeats = 1;
    ...

    loop {
        if (pointer[i] == current_pointer) {
            repeats++;
        } else {
            *current_pointer += repeats;
            current_pointer = pointer[i];
            repeats = 1;
        }
    }
我们通过计算重复相同指针的运行长度进行优化

这被案例2彻底击败,如果长跑不常见,则表现不佳


无序执行可以隐藏短期运行;只有当dep链足够长,足以填满ROB(重新排序缓冲区)时,我们才会真正暂停。

请将代码添加到问题主体。链接应该提供什么信息?案例1很快,因为编译器优化了循环。案例2是中等可能是因为cpu可以并行运行两条链,有效地将迭代次数减半。这纯粹是理论上的,还是您试图解决的实际问题?如果这是一个真正的问题,那么拥有一个指针数组,所有指针都指向同一个值将是第一个要解决的问题;)我还认为,情况3是最慢的,因为cpu不能并行运行。很难对这种情况进行优化,因为每次迭代都依赖于最后一次。@Oblivion Done。对它进行分支是我尝试的唯一尝试,并在本地工作,但通常失败。请参阅我的问题评论上的pull request链接,以了解真正的问题解释。我也将它附加在这里@Amos:正如我在回答中所说的,它只适用于案例3的非常特殊的情况。我只是在最后加上它,因为它回答了所问的具体问题,而不是一般情况。就像我说的