是否存在导致50%分支预测未命中的代码?

时间:2015-03-10 10:34:17

标签: c++ c performance compiler-optimization computer-architecture

问题:

我正在试图弄清楚如何编写代码(C优先,只有在没有其他解决方案时才支持ASM)在50%的情况下使分支预测错过。< / p>

所以它必须是一段代码“对于与分支相关的编译器优化”是“imune”,而且所有HW分支预测都不应该优于50%(抛硬币)。即使是更大的挑战,也可以在多CPU架构上运行代码,并获得相同的50%未命中率。

我设法在x86平台上编写了一个代码 47%分支未命中率。我怀疑失踪可能有3%来自:

  • 已在其中分支的程序启动开销(虽然非常小)
  • Profiler开销 - 基本上对于每个计数器读取都会引发中断,因此可能会添加其他可预测的分支。
  • 在后台运行的系统调用包含循环和可预测的分支

我编写了自己的随机数生成器,以避免调用rand,而rand的实现可能隐藏了可预测的分支。它可以在可用时使用 rdrand 。延迟对我来说无关紧要。

问题:

  1. 我可以比我的代码版做得更好吗?更好的意思是为所有CPU架构获得更高的分支错误预测和相同的结果。
  2. 此代码可以谓词吗?那是什么意思?
  3. 代码:

    #include <stdio.h>
    #include <time.h>
    
    #define RDRAND
    #define LCG_A   1103515245
    #define LCG_C   22345
    #define LCG_M   2147483648
    #define ULL64   unsigned long long
    
    ULL64 generated;
    
    ULL64 rand_lcg(ULL64 seed)
    {
    #ifdef RDRAND
        ULL64 result = 0;
        asm volatile ("rdrand %0;" : "=r" (result));
        return result;
    #else
        return (LCG_A * seed + LCG_C) % LCG_M;
    #endif
    }
    
    ULL64 rand_rec1()
    {
        generated = rand_lcg(generated) % 1024;
    
        if (generated < 512)
            return generated;
        else return rand_rec1();
    }
    
    ULL64 rand_rec2()
    {
        generated = rand_lcg(generated) % 1024;
    
        if (!(generated >= 512))
            return generated;
        else return rand_rec2();
    }
    
    #define BROP(num, sum)                  \
        num = rand_lcg(generated);          \
        asm volatile("": : :"memory");      \
        if (num % 2)                        \
            sum += rand_rec1();             \
        else                                \
            sum -= rand_rec2();
    
    #define BROP5(num, sum)     BROP(num, sum) BROP(num, sum) BROP(num, sum) BROP(num, sum) BROP(num, sum)
    #define BROP25(num, sum)    BROP5(num, sum) BROP5(num, sum) BROP5(num, sum) BROP5(num, sum) BROP5(num, sum)
    #define BROP100(num, sum)   BROP25(num, sum) BROP25(num, sum) BROP25(num, sum) BROP25(num, sum)
    
    int main()
    {
        int i = 0;
        int iterations = 500000;    
        ULL64 num = 0;
        ULL64 sum = 0;
    
        generated = rand_lcg(0) % 54321;
    
        for (i = 0; i < iterations; i++)
        {
            BROP100(num, sum);
            // ... repeat the line above 10 times
        }
    
        printf("Sum = %llu\n", sum);
    }
    

    更新v1:

    根据usr的建议,我通过在脚本中更改命令行中的LCG_C参数来生成各种模式。 我能够达到49.67%的BP错过。这对我的目的来说已经足够了,我有了在各种架构上产生这种方法的方法。

3 个答案:

答案 0 :(得分:8)

如果你知道分支预测器是如何工作的,你可以得到100%的错误预测。只需每次都对预测器进行预期的预测,然后进行相反的操作。问题是我们不知道它是如何实施的。

我已经读过,典型的预测变量可以预测0,1,0,1等模式。但我确信模式可以有多长时间。我的建议是尝试给定长度的每个模式(例如4)并查看哪一个最接近目标百分比。你应该能够同时针对50%和100%并且非常接近。需要对每个平台进行一次或在运行时进行此分析。

我怀疑分支总数的3%是在你说的系统代码中。内核不会在纯粹的CPU绑定用户代码上花费3%的开销。将调度优先级提高到最大值。

您可以通过生成一次随机数据并多次迭代相同数据来将RNG从游戏中移除。分支预测器不太可能检测到这一点(虽然它显然可以)。

我会通过填充bool[1 << 20]来实现这一点,就像我描述的那样使用零模式。然后,您可以多次运行以下循环:

int sum0 = 0, sum1 = 0;
for (...) {
 //unroll this a lot
 if (array[i]) sum0++;
 else sum1++;
}
//print both sums here to make sure the computation is not being optimized out

您需要检查反汇编以确保编译器没有做任何聪明的事情。

我不明白为什么你现在需要的复杂设置是必要的。 RNG可以解决问题,我不明白为什么需要这个简单的循环。如果编译器正在使用技巧,您可能需要将变量标记为volatile,这使得编译器(更好:大多数编译器)将它们视为外部函数调用。

由于RNG现在已不再重要,因为它几乎从不被调用,您甚至可以调用操作系统的加密RNG来获取与任何人类无法区分的数字与真正的随机数。

答案 1 :(得分:3)

使用字节填充数组,并编写一个循环,根据字节的值检查每个字节和分支。

现在仔细检查处理器的架构及其分支预测。填充数组的初始字节,以便在检查它们之后,处理器处于可预测的已知状态。从该已知状态,您可以确定是否预测下一个分支。设置下一个字节,以便预测错误。再次,找出是否预测下一个分支,并设置下一个字节,以便预测错误,依此类推。

如果你也禁用中断(这可能会改变分支预测),你可以接近100%错误预测的分支。

作为一个简单的例子,在具有强/弱预测的旧PowerPC处理器上,在三个分支之后,它将始终处于“强烈采取”状态并且一个分支未被采用将其改变为“弱吸收”。如果你现在有一系列交替的未采取/采取的分支,那么预测总是错误的,并且在弱的不采取和弱采取之间切换。

这当然只适用于那个特定的处理器。大多数现代处理器都会看到该序列几乎100%可预测。例如,他们可能会使用两个独立的预测变量;一个用于案例“最后一个分支被采取”,一个用于案例“最后一个分支未被采取”。但是对于这样的处理器,不同的字节序列将给出相同的100%误预测率。

答案 2 :(得分:0)

避免编译器优化的最简单方法是在另一个转换单元中使用void f(void) { }void g(void) { }虚函数,并禁用链接时优化。这将迫使if (*++p) f(); else g();成为一个真正不可预测的分支,假设p指向一组随机布尔值(这可以避开rand()内的分支预测问题 - 只需在测量之前执行此操作)

如果for(;;)循环给您带来问题,请输入goto

请注意&#34;循环展开技巧&#34;在评论中有点误导。您实际上创建了数千个分支。每个分支都将被单独预测,除了它们可能都不会被预测,因为CPU根本无法容纳数千个不同的预测。这可能会或可能不会对您的真正目标带来好处。