FMAF怪异的表现
fmaf weird performance
我使用fmaf
功能在*
和+
的使用情况下经历了巨大的性能降低。我在两台Linux机器上,使用G 4.4.3和G 4.6.3
在两台不同的机器上,如果填充myOut
向量而没有使用fmaf
。
带有G 4.6.3的服务器和Intel(R)Xeon(R)CPU E5-2650 @ 2.00GHz
$ ./a.out fmaf
Time: 1.55008 seconds.
$ ./a.out muladd
Time: 0.403018 seconds.
带有G 4.4.3的服务器和Intel(R)Xeon(R)CPU X5650 @ 2.67GHz
$ ./a.out fmaf
Time: 0.547544 seconds.
$ ./a.out muladd
Time: 0.34955 seconds.
不应该使用fmaf
版本(以避免额外的综述,然后更精确)吗?
#include <stddef.h>
#include <iostream>
#include <math.h>
#include <string.h>
#include <stdlib.h>
#include <sys/time.h>
int main(int argc, char** argv) {
if (argc != 2) {
std::cout << "missing parameter: 'muladd' or 'fmaf'"
<< std::endl;
exit(-1);
}
struct timeval start,stop,result;
const size_t mySize = 1e6*100;
float* myA = new float[mySize];
float* myB = new float[mySize];
float* myC = new float[mySize];
float* myOut = new float[mySize];
gettimeofday(&start,NULL);
if (!strcmp(argv[1], "muladd")) {
for (size_t i = 0; i < mySize; ++i) {
myOut[i] = myA[i]*myB[i]+myC[i];
}
} else if (!strcmp(argv[1], "fmaf")) {
for (size_t i = 0; i < mySize; ++i) {
myOut[i] = fmaf(myA[i], myB[i], myC[i]);
}
} else {
std::cout << "specify 'muladd' or 'fmaf'" << std::endl;
exit(-1);
}
gettimeofday(&stop,NULL);
timersub(&stop,&start,&result);
std::cout << "Time: " << result.tv_sec + result.tv_usec/1000.0/1000.0
<< " seconds." << std::endl;
delete []myA;
delete []myB;
delete []myC;
delete []myOut;
}
您问题的答案称为 vectorisation 。比较使用g++ -O3 -S
编译时,由G 4.4.6制成的汇编代码:
muladd
零件:
.L10:
movaps %xmm2, %xmm0
movaps %xmm2, %xmm1
movlps (%rbx,%rax), %xmm0
movlps (%r12,%rax), %xmm1
movhps 8(%rbx,%rax), %xmm0
movhps 8(%r12,%rax), %xmm1
mulps %xmm1, %xmm0
movaps %xmm2, %xmm1
movlps 0(%rbp,%rax), %xmm1
movhps 8(%rbp,%rax), %xmm1
addps %xmm1, %xmm0
movaps %xmm0, 0(%r13,%rax)
addq $16, %rax
cmpq $400000000, %rax
jne .L10
所有这些*ps
通过包装的单个精度数字执行操作。这些是SSE说明,因此每个包由每个数组的4个连续元素组成。
实现fmaf
版本的循环是:
.L14:
movss (%rbx,%r14,4), %xmm0
movss 0(%rbp,%r14,4), %xmm2
movss (%r12,%r14,4), %xmm1
call fmaf
movss %xmm0, 0(%r13,%r14,4)
addq $1, %r14
cmpq $100000000, %r14
jne .L14
此处使用标量SSE指令在一次>和上移动数据一个数组元素>在每次迭代上都对fmaf
进行函数调用。
循环的向量部分更长,但执行4倍的迭代。
intel xeon处理器不支持融合的 - 添加添加指令。Wikipedia表示,这些可在AMD PITRIVER和BULLDOZER ARCERTECTER上找到,并且直到2013/14年的Haswell/Broadwell才会介绍它们。因此,如果没有直接的指令支持,则可能将fmaf
功能汇编为模拟指令的实际功能调用。因此,有函数调用开销以及实际乘法并添加说明。非fmaf
选项会产生内联乘积并添加指令,而没有函数调用开销,因此它的速度要快得多。如有疑问,请使用g++ -S
,并检查生成的汇编代码。
此外,内联代码可以更好地优化甚至矢量化(如另一个答案中所述),但当然,其结果取决于您在编译中传递的编译器和您的确切标志的结果。