我有一个具有 AVX2 和 FMA3 的 i5-4250U。我正在我编写的 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
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 ...
所以我可以看到它已启用(只是为了确保我添加了-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
= 2.3GHZ * 8 * 2 * 2 = 73.2 GFLOPS
peak flops (with FMA) = 2 * peak flops (no FMA) = 146.2 GFLOPS
使用两个内核时,我的 CPU 在 Turbo 模式下是 2.3 GHz。我为 ILP 获得 2,因为 Ivy Bridge 可以同时进行一次 AVX 乘法和一次 AVX 加法(我已经多次展开循环以确保这一点)。
我只得到了大约 55% 的峰值翻牌(使用 FMA)。我不知道为什么,但至少我现在看到了一些东西。
一个副作用是,当我与我知道我信任的简单矩阵乘法算法进行比较时,我现在得到了一个小错误。我认为这是因为 FMA 只有一种舍入模式,而不是通常的两种(具有讽刺意味的是,它违反了 IEEE 浮点规则,尽管它可能更好)。
编辑:
有人需要重做 如何实现每个周期 4 次 FLOP 的理论最大值? 但是使用 Haswell 每个周期执行 8 个双浮点 FLOPS。
编辑
实际上,Mysticial 已经更新了他的项目以支持 FMA3(请参阅上面链接中的答案)。我用 MSVC2012 在 Windows8 中运行了他的代码(因为 Linux 版本没有在 FMA 支持下编译)。这是结果。
Testing AVX Mul + Add:
Seconds = 22.7417
FP Ops = 768000000000
FLOPs = 3.37705e+010
sum = 17.8122
Testing FMA3 FMA:
Seconds = 22.1389
FP Ops = 1536000000000
FLOPs = 6.938e+010
sum = 333.309
对于双浮点的 FMA3,这是 69.38 GFLOPS。对于单浮点,我需要将它加倍,这样就是 138.76 SP GFLOPS。我计算出我的峰值是 146.2 SP GFLOPS。 那是峰值的95%! 换句话说,我应该能够相当多地改进我的 GEMM 代码(尽管它已经比 Eigen 快了很多)。