我正在对科学应用进行一些数值优化.我注意到的一件事是GCC会pow(a,2)通过编译来优化调用a*a,但调用pow(a,6)没有优化,实际上会调用库函数pow,这会大大降低性能.(相比之下,英特尔C++编译器,可执行文件icc,将消除库调用pow(a,6).)
我很好奇的是,当我更换pow(a,6)与a*a*a*a*a*a使用GCC 4.5.1和选项" -O3 -lm -funroll-loops -msse4",它采用5分mulsd的说明:
movapd %xmm14, %xmm13
mulsd %xmm14, %xmm13
mulsd %xmm14, %xmm13
mulsd %xmm14, %xmm13
mulsd %xmm14, %xmm13
mulsd %xmm14, %xmm13
Run Code Online (Sandbox Code Playgroud)
如果我写(a*a*a)*(a*a*a),它会产生
movapd %xmm14, %xmm13
mulsd %xmm14, %xmm13
mulsd %xmm14, %xmm13
mulsd %xmm13, %xmm13
Run Code Online (Sandbox Code Playgroud)
这将乘法指令的数量减少到3. icc具有类似的行为.
为什么编译器不能识别这种优化技巧?
以下代码适用于Visual Studio 2008,有无优化.但它只适用于没有优化的G ++(O0).
#include <cstdlib>
#include <iostream>
#include <cmath>
double round(double v, double digit)
{
double pow = std::pow(10.0, digit);
double t = v * pow;
//std::cout << "t:" << t << std::endl;
double r = std::floor(t + 0.5);
//std::cout << "r:" << r << std::endl;
return r / pow;
}
int main(int argc, char *argv[])
{
std::cout << round(4.45, 1) << std::endl;
std::cout << round(4.55, 1) << std::endl;
}
Run Code Online (Sandbox Code Playgroud)
输出应该是:
4.5
4.6
Run Code Online (Sandbox Code Playgroud)
但是带有优化(O1- O3)的g ++ …
我对使用Sandy-Bridge和Haswell可以完成每个核心每个循环的触发器感到困惑.据我所知,对于SSE,每个核心每个周期应该为4个触发器,对于AVX/AVX2,每个核心每个周期应该有8个触发器.
这似乎在这里得到验证, 如何实现每个周期4个FLOP的理论最大值? ,这里, Sandy-Bridge CPU规范.
然而,下面的链接似乎表明,Sandy-bridge每个核心每个周期可以执行16个触发器,每个核心每个循环使用Haswell 32个触发器 http://www.extremetech.com/computing/136219-intels-haswell-is-an-前所未有-threat-to-nvidia-amd.
谁可以给我解释一下这个?
编辑:我现在明白为什么我感到困惑.我认为术语FLOP仅指单浮点(SP).我现在看到如何在每个循环中实现理论最大值4 FLOP的测试?实际上是双浮点(DP),因此它们为SSE实现4个DP FLOP /周期,为AVX实现8个DP FLOP /周期.在SP上重做这些测试会很有趣.
我有一个i5-4250U,它有AVX2和FMA3.我正在测试Linux上的GCC 4.8.1中的一些密集矩阵乘法代码.下面是我编译的三种不同方式的列表.
SSE2: gcc matrix.cpp -o matrix_gcc -O3 -msse2 -fopenmp
AVX: gcc matrix.cpp -o matrix_gcc -O3 -mavx -fopenmp
AVX2+FMA: gcc matrix.cpp -o matrix_gcc -O3 -march=native -fopenmp -ffast-math
Run Code Online (Sandbox Code Playgroud)
SSE2和AVX版本的性能明显不同.但是,AVX2 + FMA并不比AVX版本好.我不明白这一点.假设没有FMA,我获得了超过80%的CPU峰值触发器,但我认为我应该能够用FMA做得更好.矩阵乘法应直接受益于FMA.我基本上是在AVX中同时做八个点产品.当我检查march=native它给出:
cc -march=native -E -v - </dev/null 2>&1 | grep cc1 | grep fma
...-march=core-avx2 -mavx -mavx2 -mfma -mno-fma4 -msse4.2 -msse4.1 ...
Run Code Online (Sandbox Code Playgroud)
所以我可以看到它已启用(只是为了确保我添加-mfma但它没有区别). ffast-math应该允许宽松的浮点模型如何在SSE/AVX中使用融合乘法 - 加法(FMA)指令
编辑:
基于Mysticial的评论我继续使用_mm256_fmadd_ps,现在AVX2 + FMA版本更快. 我不确定为什么编译器不会为我这样做. 对于超过1000x1000的矩阵,我现在得到大约80 GFLOPS(没有FMA的110%的峰值触发器).如果有人不信任我的峰值翻牌计算,这就是我所做的.
peak flops (no FMA) = frequency * simd_width * ILP * cores …Run Code Online (Sandbox Code Playgroud) 我向那些(几乎)不知道GPU是如何工作的人做了一个演示.我认为说GPU有一千个核心,其中CPU只有四到八个是没有意义的.但我想给观众一个比较的元素.
在使用NVidia的Kepler和AMD的GCN架构几个月后,我很想将GPU"核心"与CPU的SIMD ALU进行比较(我不知道他们是否在英特尔有这个名称).这样公平吗?毕竟,看着汇编级时,这些编程模型有很多共同点(至少是GCN,看看p2-6中的ISA手册).
本文指出Haswell处理器每个周期可以执行32次单精度操作,但我认为有流水线或其他事情可以实现该速率.用NVidia的说法,这款处理器有多少Cuda核心?我会说每个CPU核心有8个用于32位操作,但这只是基于SIMD宽度的猜测.
当然,在比较CPU和GPU硬件时还有许多其他因素需要考虑,但这不是我想要做的.我只需要解释这件事是如何运作的.
PS:非常感谢所有指向CPU硬件文档或CPU/GPU演示的指针!
编辑: 谢谢你的回答,遗憾的是我不得不只选择其中一个.我标记了伊戈尔的答案,因为它最能贴近我最初的问题,并给了我足够的信息来证明为什么这个比较不应该太过分,但是CaptainObvious提供了非常好的文章.
使用GCC 5.3,以下代码符合 -O3 -fma
float mul_add(float a, float b, float c) {
return a*b + c;
}
Run Code Online (Sandbox Code Playgroud)
生成以下程序集
vfmadd132ss %xmm1, %xmm2, %xmm0
ret
Run Code Online (Sandbox Code Playgroud)
Clang 3.7带-O3 -mfma产品
vmulss %xmm1, %xmm0, %xmm0
vaddss %xmm2, %xmm0, %xmm0
retq
Run Code Online (Sandbox Code Playgroud)
但Clang 3.7与-Ofast -mfmaGCC生成的代码相同-O3 fast.
我很惊讶GCC的确如此,-O3因为从这个答案来看
除非允许使用宽松的浮点模型,否则不允许编译器融合分离的加法和乘法.
这是因为FMA只有一个舍入,而ADD + MUL有两个舍入.因此,编译器将通过融合违反严格的IEEE浮点行为.
但是,从这个链接说
无论FLT_EVAL_METHOD的值如何,任何浮点表达式都可以收缩,即,计算好像所有中间结果都具有无限范围和精度.
所以现在我感到困惑和担忧.
-O3?__STDC_IEC_559__不是一个矛盾吗?由于FMA 可以在软件中进行仿真,因此似乎应该有两个用于FMA的编译器开关:一个用于告诉编译器在计算中使用FMA,一个用于告诉编译器硬件具有FMA.
显然,这可以通过选项进行控制-ffp-contract.对于GCC,默认是-ffp-contract=fast和Clang不一样.其他选项例如 …
MSVC多年来一直支持AVX/AVX2指令,根据这篇msdn博客文章,它可以自动生成融合乘法加法(FMA)指令.
然而,以下两个函数都没有编译为FMA指令:
float func1(float x, float y, float z)
{
return x * y + z;
}
float func2(float x, float y, float z)
{
return std::fma(x,y,z);
}
Run Code Online (Sandbox Code Playgroud)
更糟糕的是,std :: fma没有实现为单个FMA指令,它执行速度非常快,比平原慢得多x * y + z(如果实现不依赖于FMA指令,则预期std :: fma的性能很差).
我用/arch:AVX2 /O2 /Qvec旗帜编译.也尝试过/fp:fast,没有成功.
所以问题是MSVC如何被迫自动发出FMA指令?
UPDATE
有一个#pragma fp_contract (on|off),(看起来像)什么都不做.
我想学习使用英特尔Haswell CPU微体系结构的并行编程.关于在asm/C/C++ /(任何其他语言)中使用SIMD:SSE4.2,AVX2?你能推荐书籍,教程,网络资源,课程吗?
谢谢!