使用 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
最近,我开始使用 Ubuntu 16.04 和 g++ 5.3.1,并检查我的程序 运行s 3 倍慢 。
在此之前,我使用过 Ubuntu 14.04,g++ 4.8.4。
我使用相同的命令构建它:CFLAGS = -std=c++11 -Wall -O3
.
我的程序包含循环,充满了数学调用(sin、cos、exp)。
你可以找到它 here.
我尝试使用不同的优化标志(O0、O1、O2、O3、Ofast)进行编译,但在所有情况下都会重现问题(使用 Ofast 两种变体 运行 更快,但第一个运行s 仍然慢 3 倍)。
在我的程序中我使用libtinyxml-dev
、libgslcblas
。但是它们在两种情况下都具有相同的版本,并且在性能方面(根据代码和 callgrind 分析)在程序中没有任何重要作用。
我已经进行了分析,但它并没有告诉我为什么会发生这种情况。
Kcachegrind comparison (left is slower)。
我只注意到现在程序使用 libm-2.23
与 libm-2.19
相比 Ubuntu 14.04.
我的处理器是 i7-5820,Haswell。
我不知道为什么它变慢了。你有什么想法吗?
P.S。您可以在下面找到最耗时的函数:
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 and gcc 5.3之间。只有4处不同:
- 在开头,
xorpd
被转换为 pxor
,对于相同的寄存器
- 在从 int 到 double (
cvtsi2sd
) 的转换之前添加了 pxor xmm1, xmm1
- 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 同上。不确定它会产生巨大的不同。
但是,2.23 为矢量化版本的数学函数添加了大量源代码,有些使用 AVX512 - 您的处理器可能不支持它,因为它确实是新的。也许 libm 尝试使用此类扩展,并且由于您没有它们,所以回退到通用版本?
编辑: 我尝试用 gcc 4.8.5 编译它,但为此我需要重新编译 glibc-2.19。目前我不能link,因为:
/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 中的一个错误,影响版本 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
来禁用惰性绑定.同样,上述错误报告中有更多详细信息。
最近,我开始使用 Ubuntu 16.04 和 g++ 5.3.1,并检查我的程序 运行s 3 倍慢 。
在此之前,我使用过 Ubuntu 14.04,g++ 4.8.4。
我使用相同的命令构建它:CFLAGS = -std=c++11 -Wall -O3
.
我的程序包含循环,充满了数学调用(sin、cos、exp)。 你可以找到它 here.
我尝试使用不同的优化标志(O0、O1、O2、O3、Ofast)进行编译,但在所有情况下都会重现问题(使用 Ofast 两种变体 运行 更快,但第一个运行s 仍然慢 3 倍)。
在我的程序中我使用libtinyxml-dev
、libgslcblas
。但是它们在两种情况下都具有相同的版本,并且在性能方面(根据代码和 callgrind 分析)在程序中没有任何重要作用。
我已经进行了分析,但它并没有告诉我为什么会发生这种情况。
Kcachegrind comparison (left is slower)。
我只注意到现在程序使用 libm-2.23
与 libm-2.19
相比 Ubuntu 14.04.
我的处理器是 i7-5820,Haswell。
我不知道为什么它变慢了。你有什么想法吗?
P.S。您可以在下面找到最耗时的函数:
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 and gcc 5.3之间。只有4处不同:
- 在开头,
xorpd
被转换为pxor
,对于相同的寄存器 - 在从 int 到 double (
cvtsi2sd
) 的转换之前添加了 - a
movsd
刚好在转换之前移动了 - 加法 (
addsd
) 被移动到比较 (ucomisd
) 之前
pxor xmm1, xmm1
所有这些可能都不足以导致性能下降。拥有一个好的分析器(例如英特尔)可能会更有说服力,但我没有权限。
现在,存在对 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 同上。不确定它会产生巨大的不同。
但是,2.23 为矢量化版本的数学函数添加了大量源代码,有些使用 AVX512 - 您的处理器可能不支持它,因为它确实是新的。也许 libm 尝试使用此类扩展,并且由于您没有它们,所以回退到通用版本?
编辑: 我尝试用 gcc 4.8.5 编译它,但为此我需要重新编译 glibc-2.19。目前我不能link,因为:
/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 中的一个错误,影响版本 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
来禁用惰性绑定.同样,上述错误报告中有更多详细信息。