由于if语句导致C ++大量性能下降

时间:2015-10-29 13:12:10

标签: c++ multithreading optimization

我在循环中运行4个线程,在循环中我正在评估函数并逐步增加计数器。

while(1) {
    int fitness = EnergyFunction::evaluate(sequence);

    mutex.lock();
    counter++;
    mutex.unlock();
}

当我运行这个循环时,就像我在4个运行线程中所说的那样,我每秒得到~20 000 000个评估。

while(1) {
    if (dist(mt) == 0) {
        sequence[distDim(mt)] = -1;
    } else {
        sequence[distDim(mt)] = 1;
    }
    int fitness = EnergyFunction::evaluate(sequence);

    mainMTX.lock();
    overallGeneration++;
    mainMTX.unlock();
}

如果我为序列添加一些随机变异,我每秒得到~13 000 000次评估。

while(1) {
    if (dist(mt) == 0) {
        sequence[distDim(mt)] = -1;
    } else {
        sequence[distDim(mt)] = 1;
    }
    int fitness = EnergyFunction::evaluate(sequence);

    mainMTX.lock();
    if(fitness < overallFitness)
        overallFitness = fitness;

    overallGeneration++;
    mainMTX.unlock();
}

但是当我添加简单的if语句进行检查时,如果新的适应度小于旧的适应性,如果这是真的,那么用新的适应性替换旧的健身。

但是性能损失是巨大的!现在我每秒得到~20 000次评估。如果我删除随机变异部分,我每秒也会得到~20 000个评估。

变量overallFitness声明为

extern int overallFitness; 

我遇到了麻烦,想知道如此大的性能损失是什么问题。比较两个int这样的时间采取操作?

此外,我不相信这与互斥锁定有关。

更新

此性能损失不是因为分支预测,而是编译器忽略了此调用int fitness = EnergyFunction::evaluate(sequence);

现在我添加了volatile,编译器不再忽略该调用。

还要感谢你指出分支机构的错误预测和atomic<int>,并不了解它们!

因为原子我也删除了互斥部分,所以最终代码如下所示:

while(1) {
    sequence[distDim(mt)] = lookup_Table[dist(mt)];
    fitness = EnergyFunction::evaluate(sequence);
    if(fitness < overallFitness)
       overallFitness = fitness;
    ++overallGeneration;
}

现在我每秒得到~25000次评估。

4 个答案:

答案 0 :(得分:31)

您需要运行一个分析器才能到达底层。在Linux上,使用perf

我的猜测是EnergyFunction::evaluate()正在完全优化,因为在第一个示例中,您不使用结果。所以编译器可以丢弃整个事情。您可以尝试将返回值写入volatile变量,这会强制编译器或链接器不优化调用。加速1000倍绝对不是一个简单的比较。

答案 1 :(得分:11)

实际上有一个原子指令可以将int增加1.因此,智能编译器可能能够完全删除互斥锁,尽管如此,我会感到惊讶。您可以通过查看程序集,或删除互斥锁并将overallGeneration的类型更改为atomic<int>来检查它,以检查它仍然有多快。最后一个缓慢的例子不再可能进行这种优化。

此外,如果编译器可以看到evaluate对全局状态不执行任何操作并且未使用结果,则它可以跳过对evaluate的整个调用。您可以通过查看程序集或删除对EnergyFunction::evaluate(sequence)的调用来查看是否是这种情况,并查看时间 - 如果它没有加速,则首先不调用该函数。最后一个慢速示例不再可能进行此优化。您应该能够通过在不同的目标文件(其他cpp或库)中定义函数并禁用链接时间优化来阻止编译器不执行EnergyFunction::evaluate(sequence)

这里还有其他效果也会产生性能差异,但是我看不到任何可以解释因子1000差异的其他效果。因子1000通常意味着编译器在之前的测试中被欺骗了,现在改变了它来自作弊。

答案 2 :(得分:7)

我不确定我的答案会给出如此戏剧性的表现下降的解释,但它肯定会对它产生影响。

在第一种情况下,您将分支添加到非关键区域:

if (dist(mt) == 0) {
    sequence[distDim(mt)] = -1;
} else {
    sequence[distDim(mt)] = 1;
}

在这种情况下,CPU(至少IA)将执行分支预测,并且在分支未命中预测的情况下存在性能损失 - 这是已知的事实。

现在关于第二次添加,您在 critical 区域添加了一个分支:

mainMTX.lock();
if(fitness < overallFitness)
    overallFitness = fitness;

overallGeneration++;
mainMTX.unlock();

反过来,除了“未命中预测”惩罚之外,还增加了在该区域中执行的代码量,从而增加了其他线程必须等待mainMTX.unlock();的概率。

注意

请确保将所有全局/共享资源定义为volatile。否则编译器可能会优化它们(这可能在一开始就解释了如此多的评估)。

如果overallFitness,它可能不会被优化,因为它被声明为externoverallGeneration可能会被优化掉。如果是这种情况,则可以在 critical 区域中添加“真实”内存访问后解释此性能下降。

<强>注2

我仍然不确定我提供的解释是否可以解释这种显着的性能下降。所以我相信代码中可能会有一些您没有发布的实现细节(例如volatile)。

修改

正如彼得(@Peter) Mark Lakata (@MarkLakata)在单独的答案中所述,我倾向于同意他们,很可能是因为性能下降是在第一种情况下fitness从未使用过,因此编译器只是将该变量与函数调用一起优化。在第二种情况下使用了fitness,因此编译器没有优化它。彼得和马克很好!我错过了这一点。

答案 3 :(得分:2)

我意识到这不是问题的答案,而是问题的替代方案。

代码运行时是否使用overallGeneration?也就是说,它可能用于确定何时停止计算?如果不是,则可以放弃同步全局计数器并在每个线程上有一个计数器,并在计算完成后,将所有每线程计数器总计为总计。类似地,对于overallFitness,您可以跟踪每个线程的maxFitness,并在计算结束后选择四个结果中的最大值。

根本没有线程同步会让你100%的CPU使用率。