3

我想编写一个函数,该函数将根据用户设置执行或不执行一些可选代码。该函数是 cpu 密集型的,并且其中包含 ifs 会很慢,因为分支预测器不是那么好。

我的想法是在函数的内存中创建一个副本,并在我不想执行某些代码时用跳转替换 NOP。我的工作示例是这样的:

int Test()
{
    int x = 2;
    for (int i=0 ; i<10 ; i++)
    {
        x *= 2;

        __asm {NOP}; // to skip it replace this
        __asm {NOP}; // by JMP 2 (after the goto)
            x *= 2; // Op to skip or not

        x *= 2;
    }
    return x;
}

在我的测试主程序中,我将此函数复制到新分配的可执行内存中,并用 JMP 2 替换 NOP,以便不执行以下 x *= 2。JMP 2 真的是“跳过接下来的 2 个字节”。

问题是每次编辑要跳过的代码并更改其大小时,我都必须更改 JMP 操作数。

可以解决此问题的替代方法是:

__asm {NOP}; // to skip it replace this
__asm {NOP}; // by JMP 2 (after the goto)
goto dont_do_it;
    x *= 2; // Op to skip or not
dont_do_it:
x *= 2;

然后我想跳过或不跳过具有固定大小的 goto。不幸的是,在完全优化模式下,goto 和 x*=2 被删除,因为它们在编译时无法访问。

因此需要保留该死代码。

我正在使用 VStudio 2008。

4

7 回答 7

6

您可以将分支的成本最多减少 10,只需将其移出循环即可:

int Test()
{
    int x = 2;
    if (should_skip) {
        for (int i=0 ; i<10 ; i++)
        {
            x *= 2;
            x *= 2;
        }
    } else {
        for (int i=0 ; i<10 ; i++)
        {
            x *= 2;
            x *= 2;
            x *= 2;
        }
    }

    return x;
}

在这种情况下,以及其他类似的情况,这也可能促使编译器更好地优化循环体,因为它会分别考虑这两种可能性,而不是尝试优化条件代码,并且它不会优化任何东西死了。

如果这导致太多重复的代码无法维护,请使用通过引用获取 x 的模板:

    int x = 2;
    if (should_skip) {
        doLoop<true>(x);
    } else {
        doLoop<false>(x);
    }

并检查编译器是否内联它。

显然,这会稍微增加代码大小,这有时会成为一个问题。但是,无论您采用哪种方式,如果此更改不会产生可衡量的性能改进,那么我猜您的也不会。

于 2010-04-01T20:32:04.863 回答
4

如果代码的排列数量合理,您可以将代码定义为 C++ 模板并生成所有变体。

于 2010-04-01T20:27:13.700 回答
4

您没有指定您使用的编译器和平台,这将阻止大多数人能够帮助您。例如,在某些平台上,代码部分是不可写的,因此您将无法用 JMP 替换 NOP。

您正在尝试挑选编译器提供给您的优化并对其进行猜测。一般来说,这是一个坏主意。要么在汇编中编写整个内部循环块,这将防止编译器消除死代码,要么将该死的 if 语句放在那里,让编译器做它的事情。

我也怀疑分支预测是否足够糟糕,你会从做你提议的事情中获得任何形式的净胜利。您确定这不是过早优化的情况吗?您是否以最明显的方式编写代码,然后才确定其性能不够好?那将是我建议的开始。

于 2010-04-01T20:29:17.303 回答
1

这是实际问题的实际答案!

volatile int y = 0;

int Test() 
{
    int x = 2; 
    for (int i=0 ; i<10 ; i++) 
    { 
        x *= 2; 

        __asm {NOP}; // to skip it replace this 
        __asm {NOP}; // by JMP 2 (after the goto) 
        goto dont_do_it;
    keep_my_code:
        x *= 2; // Op to skip or not 
    dont_do_it: 
        x *= 2; 
    }
    if (y) goto keep_my_code;
    return x; 
} 
于 2010-04-01T20:42:40.743 回答
0

这是x64吗?您也许可以使用函数指针和条件移动来避免分支预测器。根据用户设置加载程序的地址;其中一个程序可能是一个什么都不做的假人。您应该能够在没有任何内联 ASM 的情况下完成此操作。

于 2010-04-01T20:28:11.613 回答
0

这可能会提供见解:

#pragma 为 Visual Studio 优化

也就是说,对于这个特定的问题,我将使用 VS asm 输出作为参考点,将代码手工编码到 ASM 中。

在元级别,我必须非常确定这是我开始优化 CPU 管道之前所做工作的最佳设计和算法。

于 2010-04-01T21:15:42.220 回答
0

如果你让它工作,那么我会分析它以确保它对你来说真的更快。在现代 CPU 上,几乎没有什么比修改已经在 cpu 缓存中的代码更慢的事情,或者更糟糕的是,cpu 管道。cpu 基本上必须扔掉管道中的所有工作并重新开始。

于 2010-04-01T22:48:34.787 回答