使用g++ 5.3.1编译的程序比使用相同命令并用g++ 4.8.4编译的程序运行速度慢三倍。

17
最近,我开始使用Ubuntu 16.04和g++ 5.3.1,并发现我的程序运行慢了3倍。之前我使用的是Ubuntu 14.04和g++ 4.8.4。我用相同的命令构建它:CFLAGS = -std=c++11 -Wall -O3
我的程序包含循环,其中填充了数学调用(sin,cos,exp)。你可以在这里找到它。
我尝试使用不同的优化标志(O0、O1、O2、O3、Ofast)进行编译,但在所有情况下都会出现问题(使用Ofast两个变量都运行得更快,但第一个仍然慢了3倍)。
在我的程序中,我使用libtinyxml-devlibgslcblas。但是它们在两种情况下都有相同的版本,并且在性能方面(根据代码和callgrind分析)对程序没有任何重要影响。
我已经进行了性能分析,但它并没有给我任何关于为什么会出现这种情况的想法。Kcachegrind比较(左边更慢)。我只注意到现在程序使用libm-2.23,而不是Ubuntu 14.04中的libm-2.19
我的处理器是i7-5820,Haswell。
我不知道为什么它变得更慢了。你有任何想法吗?
附言:下面是最耗时的函数。
void InclinedSum::prepare3D()
{
double buf1, buf2;
double sum_prev1 = 0.0, sum_prev2 = 0.0;
int break_idx1, break_idx2; 
int arr_idx;

for(int seg_idx = 0; seg_idx < props->K; seg_idx++)
{
    const Point& r = well->segs[seg_idx].r_bhp;

    for(int k = 0; k < props->K; k++)
    {
        arr_idx = seg_idx * props->K + k;
        F[arr_idx] = 0.0;

        break_idx2 = 0;

        for(int m = 1; m <= props->M; m++)
        {
            break_idx1 = 0;

            for(int l = 1; l <= props->L; l++)
            {
                buf1 = ((cos(M_PI * (double)(m) * well->segs[k].r1.x / props->sizes.x - M_PI * (double)(l) * well->segs[k].r1.z / props->sizes.z) -
                            cos(M_PI * (double)(m) * well->segs[k].r2.x / props->sizes.x - M_PI * (double)(l) * well->segs[k].r2.z / props->sizes.z)) /
                        ( M_PI * (double)(m) * tan(props->alpha) / props->sizes.x + M_PI * (double)(l) / props->sizes.z ) + 
                            (cos(M_PI * (double)(m) * well->segs[k].r1.x / props->sizes.x + M_PI * (double)(l) * well->segs[k].r1.z / props->sizes.z) -
                            cos(M_PI * (double)(m) * well->segs[k].r2.x / props->sizes.x + M_PI * (double)(l) * well->segs[k].r2.z / props->sizes.z)) /
                        ( M_PI * (double)(m) * tan(props->alpha) / props->sizes.x - M_PI * (double)(l) / props->sizes.z )
                            ) / 2.0;

                buf2 = sqrt((double)(m) * (double)(m) / props->sizes.x / props->sizes.x + (double)(l) * (double)(l) / props->sizes.z / props->sizes.z);

                for(int i = -props->I; i <= props->I; i++)
                {   

                    F[arr_idx] += buf1 / well->segs[k].length / buf2 *
                        ( exp(-M_PI * buf2 * fabs(r.y - props->r1.y + 2.0 * (double)(i) * props->sizes.y)) - 
                        exp(-M_PI * buf2 * fabs(r.y + props->r1.y + 2.0 * (double)(i) * props->sizes.y)) ) *
                        sin(M_PI * (double)(m) * r.x / props->sizes.x) * 
                        cos(M_PI * (double)(l) * r.z / props->sizes.z);
                }

                if( fabs(F[arr_idx] - sum_prev1) > F[arr_idx] * EQUALITY_TOLERANCE )
                {
                    sum_prev1 = F[arr_idx];
                    break_idx1 = 0;
                } else
                    break_idx1++;

                if(break_idx1 > 1)
                {
                    //std::cout << "l=" << l << std::endl;
                    break;
                }
            }

            if( fabs(F[arr_idx] - sum_prev2) > F[arr_idx] * EQUALITY_TOLERANCE )
            {
                sum_prev2 = F[arr_idx];
                break_idx2 = 0;
            } else
                break_idx2++;

            if(break_idx2 > 1)
            {
                std::cout << "m=" << m << std::endl;
                break;
            }
        }
    }
}
}

进一步调查。 我编写了以下简单程序:

#include <cmath>
#include <iostream>
#include <chrono>

#define CYCLE_NUM 1E+7

using namespace std;
using namespace std::chrono;

int main()
{
    double sum = 0.0;

    auto t1 = high_resolution_clock::now();
    for(int i = 1; i < CYCLE_NUM; i++)
    {
        sum += sin((double)(i)) / (double)(i);
    }
    auto t2 = high_resolution_clock::now();

    microseconds::rep t = duration_cast<microseconds>(t2-t1).count();

    cout << "sum = " << sum << endl;
    cout << "time = " << (double)(t) / 1.E+6 << endl;

    return 0;
}

我很想知道为什么这个简单的示例程序在g++ 4.8.4 libc-2.19 (libm-2.19)下比在g++ 5.3.1 libc-2.23 (libm-2.23)下快2.5倍。

编译命令如下:

g++ -std=c++11 -O3 main.cpp -o sum

使用其他优化标志不改变比率。

我如何理解是谁,gcc还是libc,使程序变慢?


3
我猜测是 libm 自身导致的(它实现了那些数学函数——是的,它们没有被内联)。 - Paul Stelian
1
你看过生成的汇编输出了吗? - orbitcowboy
1
另外,你确定这个函数没有bug吗?也就是说,没有未定义的行为?你有数组或类似数组的类型(比如F,如果你问我,这是一个糟糕的名字),并且你正在使用未经检查的索引来访问它们,以查看它们是否在边界内。如果汇编输出相同或大致相等,请检查函数本身,因为未定义的行为可能会产生奇怪的结果。 - PaulMcKenzie
1
@orbitcowboy,我还没有查看汇编清单。这里是源代码的清单,包括主要计算。 - Alex Novikov
1
好的,那我会尝试找出瓶颈。1.通过注释来缩减代码。2.测量时间。如果代码仍然很慢,请继续执行步骤1。 - orbitcowboy
显示剩余12条评论
2个回答

4

为了得到一个非常精准的答案,你可能需要libm维护者来查看你的问题。但是,这是我的看法——如果我发现其他内容,我会将其添加到这个答案中。

首先,看一下GCC生成的汇编代码,在gcc 4.8.2gcc 5.3之间。只有4个差异:

  • 在开头,xorpd被转换为相同寄存器的pxor
  • 在从int转换为double(cvtsi2sd)之前添加了pxor xmm1,xmm1
  • movsd移动到转换之前
  • 将加法(addsd)移动到比较(ucomisd)之前

所有这些可能不足以导致性能下降。使用优秀的分析工具(例如英特尔),可以得出更具决定性的结论,但我无法访问其中任何一个。

现在,这里有一个对sin的依赖,让我们看看发生了什么变化。问题首先是确定您使用的平台... glibc的sysdeps中有17个不同的子文件夹(其中定义了sin),因此我选择了x86_64

首先,处理处理器功能的方式已更改,例如glibc/sysdeps/x86_64/fpu/multiarch/s_sin.c在2.19中用于检查FMA / AVX,但在2.23中外部完成。可能存在一个错误,在该错误中未正确报告功能,导致未使用FMA或AVX。然而,我认为这种假设并不十分可信。

其次,在.../x86_64/fpu/s_sinf.S中,唯一的修改(除版权更新外)是更改堆栈偏移量,将其对齐到16字节;sincos也是如此。不确定它会有很大的区别。

然而,2.23版本增加了许多向量化数学函数的源代码,并且一些使用了AVX512——这可能是因为你的处理器太新不支持。也许libm试图使用这些扩展,但由于你没有它们,所以回退到通用版本?
编辑:我尝试使用gcc 4.8.5编译它,但是我需要重新编译glibc-2.19。目前我无法链接,因为出现了这个问题:
/usr/lib/gcc/x86_64-linux-gnu/4.8/../../../x86_64-linux-gnu/libm.a(s_sin.o): in function « __cos »:
(.text+0x3542): undefined reference to « _dl_x86_cpu_features »
/usr/lib/gcc/x86_64-linux-gnu/4.8/../../../x86_64-linux-gnu/libm.a(s_sin.o): in function « __sin »:
(.text+0x3572): undefined reference to « _dl_x86_cpu_features »

我会尝试解决这个问题,但请注意,很可能这个符号是根据处理器选择正确优化版本的原因之一,这可能会影响性能。


1
这是glibc的一个bug,影响版本为2.23(在Ubuntu 16.04中使用)和早期版本的2.24(例如Fedora和Debian已经包含了修补程序版本,不再受影响,但Ubuntu 16.10和17.04尚未更新)。
减速源于SSE到AVX寄存器转换惩罚。请参见glibc错误报告:https://sourceware.org/bugzilla/show_bug.cgi?id=20495 Oleg Strikov在他的Ubuntu错误报告中撰写了一份相当详尽的分析:https://bugs.launchpad.net/ubuntu/+source/glibc/+bug/1663280 如果没有修补程序,有各种可能的解决方法:您可以静态编译问题(即添加-static),或者在程序执行期间设置环境变量LD_BIND_NOW来禁用延迟绑定。同样,更多详细信息请参阅上述错误报告。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接