很抱歉,这可能是一个过于抽象的问题,但对我来说这是非常实用的+可能是一些专家有类似的经验并且可以解释它。
我有一个大代码,大约10000行。
我注意到如果在某个地方我放
if ( expression ) continue;
其中表达式总是为假(用代码和cout逻辑双重检查),但取决于未知参数(因此编译器不能简单地在编译期间摆脱这一行)程序的速度增加25%(计算结果相同)。如果我测量环路本身的速度,则加速因子大于3。
为什么会发生这种情况?如果没有这些技巧,有什么方法可以使用这种加速的可能性?
P.S。我使用gcc 4.7.3,-O3优化。
更多信息:
我尝试了两种不同的表达方式,都是有效的。
如果我将该行更改为:
if ( expression ) { cout << " HELLO " << endl; continue; };
加速消失了。
如果我将该行更改为:
expression;
加速消失了。
围绕该行的代码如下所示:
for ( int i = a; ; ) {
do {
i += d;
if ( d*i > d*ilast ) break;
// small amount of calculations, and conditional calls of continue;
} while ( expression0 );
if ( d*i > dir*ilast ) break;
if ( expression ) continue;
// very big amount calculations, and conditional calls of continue;
}
for循环看起来很奇怪。这是因为我修改了环以便抓住这个瓶颈。最初表达式等于表达式0而不是do-loop我只有这个继续。
我尝试使用__builtin_expect来理解分支预测。与
// the expression (= false) is supposed to be true by branch prediction.
if ( __builtin_expect( !!(expression), 1) ) continue;
加速是25%。
// the expression (= false) is supposed to be false by branch prediction.
if ( __builtin_expect( !!(expression), 0) ) continue;
加速消失了。
如果我使用-O2而不是-O3,效果就会消失。代码比具有错误条件的快速O3版本稍微(~3%)慢。
“-O2 -finline-functions -funswitch-loops -fpredictive-commoning -fgcse-after-reload -ftree-vectorize”相同。还有一个选项:“-O2 -finline-functions -funswitch-loops -fpredictive-commoning -fgcse-after-reload -ftree-vectorize -fipa-cp-clone”,效果被放大。使用“线”速度相同,没有“线”代码慢75%。
原因在于仅遵循条件运算符。所以代码看起来像这样:
for ( int i = a; ; ) {
// small amount of calculations, and conditional calls of continue;
if ( expression ) continue;
// calculations1
if ( expression2 ) {
// calculations2
}
// very big amount calculations, and conditional calls of continue;
}
expression2的值几乎总是假的。所以我改变了这样:
for ( int i = a; ; ) {
// small amount of calculations, and conditional calls of continue;
// if ( expression ) continue; // don't need this anymore
// calculations1
if ( __builtin_expect( !!(expression2), 0 ) ) { // suppose expression2 == false
// calculations2
}
// very big amount calculations, and conditional calls of continue;
}
并且希望加速25%。甚至更多一点。行为不再取决于关键线。
如果有人知道材料,可以在没有猜测的情况下解释这种行为,我将非常乐意阅读并接受他们的回答。
答案 0 :(得分:3)
找到它。
原因在于紧随其后的条件运算符。所以代码看起来像这样:
for ( int i = a; ; ) {
// small amount of calculations, and conditional calls of continue;
if ( expression ) continue;
// calculations1
if ( expression2 ) {
// calculations2
}
// very big amount calculations, and conditional calls of continue;
}
expression2的值几乎总是假的。所以我改变了这样:
for ( int i = a; ; ) {
// small amount of calculations, and conditional calls of continue;
// if ( expression ) continue; // don't need this anymore
// calculations1
if ( __builtin_expect( !!(expression2), 0 ) ) { // suppose expression2 == false
// calculations2
}
// very big amount calculations, and conditional calls of continue;
}
并且希望加速25%。甚至更多一点。行为不再取决于关键线。
我不确定如何解释它,也找不到足够的分支预测材料。
但我想重点是应该跳过calculate2,但编译器不知道这一点,默认情况下假设expression2 == true。 同时它假设在简单的继续检查中
if ( expression ) continue;
表达式== false,并且在任何情况下都必须完成跳过计算2。 如果我们有更复杂的操作(例如cout),则假设表达式为真且技巧不起作用。
如果有人知道材料,可以在没有猜测的情况下解释这种行为,我将非常乐意阅读并接受他们的回答。
答案 1 :(得分:0)
引入那个不可能达到的分支分解了流程图。通常,编译器知道执行流程是从循环的顶部直接到退出测试并再次返回到开始。现在图中有一个额外的节点,流可以离开循环。现在需要以不同的方式编译循环体,分为两部分。
这几乎总会导致更糟糕的代码。为什么它不在这里,我只能提供一个猜测:你没有使用分析信息进行编译。因此,编译器必须做出假设。特别是,它必须假设在运行时采用分支的可能性。
显然,由于它必须做出的假设是不同的,因此结果代码的速度很可能不同。
答案 2 :(得分:0)
我不想这么说,但答案是非常技术性的,更重要的是,对你的代码非常具体。这么多,可能没有人会花时间来调查问题的根源。正如其他人所建议的那样,它很可能依赖于分支预测和与流水线相关的其他编译后优化。
我唯一可以建议帮助您缩小范围,如果这是编译器优化问题或编译后(CPU)优化,那就是再次编译代码,-O2
vs {{1但是这次添加了以下附加选项:-O3
。将每个输出传递给两个不同的文件,然后运行像sdiff之类的东西来比较它们。你应该看到很多不同之处。
不幸的是,如果对汇编代码没有很好的理解,就很难做出正面或反面的内容,老实说,Stack Overflow上没有多少人有耐心(或时间)花费超过几分钟的时间问题。如果你不能流利组装(大概是x86),那么我建议找一个同事或朋友来帮助你解析汇编输出。