使用g++ 5.3.1编译的程序比使用g++ 4.8.4(相同的命令)编译的程序运行速度慢3倍

The program runs 3 times slower when compiled with g++ 5.3.1 than the same program compiled with g++ 4.8.4, the same command

本文关键字:g++ 程序 编译 命令 3倍 速度慢 运行 使用      更新时间:2023-10-16

最近,我开始使用Ubuntu 16.04和g++ 5.3.1,并检查我的程序运行慢了3倍。在此之前,我用过Ubuntu 14.04, g++ 4.8.4。我用同样的命令构建它:CFLAGS = -std=c++11 -Wall -O3 .

我的程序包含循环,充满了数学调用(sin, cos, exp)。你可以在这里找到。

我试着用不同的优化标志(0,O1, O2, O3, Ofast)进行编译,但在所有情况下,问题都重现了(与Ofast两个变体运行得更快,但第一个运行速度仍然慢3倍)。

在我的程序中我使用libtinyxml-dev, libgslcblas。但是在这两种情况下,它们具有相同的版本,并且在程序中不占任何重要的部分(根据代码和callgrind分析)。

我已经执行了分析,但它没有给我任何关于为什么会发生的想法。Kcachegrind比较(左边比较慢)。我只注意到现在的程序使用libm-2.23相比libm-2.19在Ubuntu 14.04。

我的处理器是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减慢了程序?

要得到一个真正精确的答案,您可能需要一个libm维护者来查看您的问题。然而,这是我的看法——把它当作草稿吧,如果我发现其他的东西,我会把它添加到这个答案中。

首先,看看GCC生成的asm,在GCC 4.8.2和GCC 5.3之间。只有4个不同之处:

  • 在开始的xorpd被转换成pxor,对于相同的寄存器
  • 在int到double的转换之前添加了一个pxor xmm1, xmm1 (cvtsi2sd)
  • a movsd在转换前被移动
  • 添加(addsd)在比较(ucomisd)之前被移动

所有这些可能不足以降低性能。有一个好的分析器(例如英特尔)可以让我们得出更有说服力的结论,但我没有这样的分析器。

现在,有一个对sin的依赖,所以让我们看看有什么变化。问题是首先要确定你使用的是什么平台……在glibc的sysdeps(其中定义了sin)中有17个不同的子文件夹,所以我选择了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的Idem。我不确定这会不会有很大的不同。

然而,2.23增加了很多数学函数矢量化版本的源代码,有些使用了AVX512——你的处理器可能不支持,因为它真的是新的。也许libm尝试使用这样的扩展,因为你没有它们,退回到通用版本?

EDIT:我尝试用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 »

我将尝试解决这个问题,但事先请注意,这个符号很可能负责根据处理器选择正确的优化版本,这可能是性能下降的一部分。

这是glibc中的一个bug,影响2.23版本(在Ubuntu 16.04中使用)和2.24早期版本(例如Fedora和Debian已经包含不再受影响的补丁版本,Ubuntu 16.10和17.04还没有)。

减速源于SSE到AVX寄存器的转换惩罚。查看这里的glibc bug报告:https://sourceware.org/bugzilla/show_bug.cgi?id=20495

Oleg Strikov在他的Ubuntu bug报告中写了一个相当广泛的分析:https://bugs.launchpad.net/ubuntu/+source/glibc/+bug/1663280

没有补丁,有各种可能的解决方法:你可以静态编译你的问题(即添加-static),或者你可以通过在程序执行期间设置环境变量LD_BIND_NOW来禁用延迟绑定。同样,在上面的bug报告中有更多的细节。