我想编写一个函数,根据用户设置,可以执行或不执行某些可选代码。该函数是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。
答案 0 :(得分: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);
}
并检查编译器是否内联。
显然,这会稍微增加代码大小,这偶尔会引起关注。无论你采用哪种方式,如果这种变化不能产生可衡量的性能提升,那么我猜你的也不会。
答案 1 :(得分:4)
如果代码的排列数量合理,您可以将代码定义为C ++模板并生成所有变体。
答案 2 :(得分:4)
您没有指定使用的编译器和平台,这会阻止大多数人帮助您。例如,在某些平台上,代码部分不可写,因此您将无法用JMP替换NOP。
您正在尝试挑选编译器提供给您的优化并进行二次猜测。一般来说,这是一个坏主意。要么在汇编中编写整个内部循环块,这会阻止编译器消除死代码,或者将死的if语句放在那里,让编译器做它的事情。
我也很怀疑分支预测是否足够糟糕,你可以从你提出的建议中获得任何一种净胜利。你确定这不是过早优化的情况吗?您是否以最明显的方式编写代码,然后才确定其性能不够好?这将是我建议的开始。
答案 3 :(得分: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;
}
答案 4 :(得分:0)
这是x64吗?您可以使用函数指针和条件移动来避免分支预测器。根据用户设置加载程序的地址;其中一个程序可能是一个什么都不做的假人。您应该可以在没有任何内联ASM的情况下执行此操作。
答案 5 :(得分:0)
这可能会提供见解:
#pragma optimize for Visual Studio
那就是说,对于这个特殊的问题,我会手动编码到ASM中,使用VS asm输出作为参考点。
在元级别,我必须非常肯定这是最好的设计&amp;在我开始优化CPU管道之前,我正在做的算法。
答案 6 :(得分:0)
如果你让它工作,那么我会对它进行分析,以确保它对你来说真的更快。在现代CPU上,你可以做的很少,比修改已经在cpu缓存中的代码,或者更糟糕的是cpu管道要慢。 cpu基本上必须抛弃管道中的所有工作并重新开始。