【问题标题】:How to create branchless code for this piece of code?如何为这段代码创建无分支代码?
【发布时间】:2018-04-24 18:03:13
【问题描述】:

我需要为内部循环中的 if 语句生成无分支代码 如果(我!= j)。我很困惑如何生成无分支代码。

  for (int i = start; i < n; i++)
  {
        results[i] = 999;
        for (int j = 0; j < n; j++)
        {
            if (i != j)
            {
                d = myfunction(x, y, i, j);
                if (d < results[i])
                    results[i] = d;
            }
        }
    }

【问题讨论】:

    标签: compiler-construction computer-science compiler-optimization cpu-architecture micro-optimization


    【解决方案1】:

    在 C++ 中,比较返回 0(假)或 1(真)。因此,您可以将最里面的条件转换如下:

    int condition = d < results[i] 
    results[i] = d * condition + results[i] * !condition;
    

    要跳过内部循环中的 i,只需在 i 及以后的 arg 中添加 1:

    ...
    for (int j = 0; j < n - 1; j++) {
        d = myfunction(x, y, i, j + (j >= i));
        int condition = d < results[i] 
        results[i] = d * condition + results[i] * !condition;
    }
    ...
    

    比较少的另一种方法是将内部循环分成两个循环:

    for (int j = 0; j < i; j++) {
       ...
    }
    for (int j = i + i; j < n; j++) {
       ...
    }
    

    编辑:复杂增量/循环开始重整被替换。

    P.S.:优化选项可能是在局部变量中构建最小值并仅在内循环之后分配给 results[i]。

    【讨论】:

    • 程序会崩溃,假设我有一个size=5的数组,当i=4时,j=4则j+=(i==j);使 j=5 这超出数组的索引。
    • 它如何/为什么会崩溃?
    • 循环条件是一个分支,因此与在循环体中放置一个额外的分支相比,拆分内部循环确实减少了分支。但它并非完全没有分支。某些种类的分支预测硬件可能已经预测了n 迭代循环的多次重复的循环终止,但对于不同的长度则不太好。因此,这可能是对错误预测的洗礼,但仍然是指令数/吞吐量的胜利。 (对于实际使用,我绝对推荐循环拆分,尤其是对于大型 n。无分支方式为罕见情况增加了大量工作)
    • 糟糕,已调整循环条件以解决此问题
    • 附注摆脱了对 j 的额外分配
    【解决方案2】:

    如果我理解正确,您需要对n by n 矩阵进行操作,但不包括对角线中的值,即:

    X O O O
    O X O O
    O O X O
    O O O X
    

    您可以通过像这样“展开”矩阵来重新定义问题:

    . O O O
    . . O O O
    . . . O O O
    . . . . O O O
    

    然后你可以在内部循环中更正j

    for (int i = 0; i < n; i++) {
        // ...
        for (int j = i + 1; j < i + n; j++) {
            d = myfunction(x, y, i, j % n);
            // ...
        }
    }
    

    【讨论】:

    • j%n 是无分支的,但速度很慢,除非编译器将其转换为 compare/cmov 而不是实际的除法。 (或者如果 n 是一个编译时常量,那也不错。)在源代码中,你可以这样做 jmod++; jmod = (jmod == n) ? 0 : jmod;
    • @PeterCordes:好点,顺便提一下,由于 j 是有界的,我们可以将 j%n 替换为 j-n 以达到相同的结果:D(答案已更新)
    • 这不会使 j
    • 糟糕。是的。这就是我用 Python 编写代码所得到的(它很乐意接受负索引)。
    • 在这种情况下 j=i+1,j 永远不会为零?