2

fmaf使用该功能而不是使用*and时,我遇到了巨大的性能下降+。我在两台 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;
}
4

2 回答 2

2

据我所知,英特尔至强处理器不支持融合乘加指令。Wikipedia 指出这些可用于 AMD Piledriver 和 Bulldozer 架构处理器,英特尔将在 2013/14 年的 Haswell/Broadwell 之前推出它们。因此,如果没有直接的指令支持,该fmaf函数很可能被编译为模拟指令的实际函数调用。因此,存在函数调用开销以及实际的乘法和加法指令。non-fmaf选项产生内联乘法和加法指令,没有函数调用开销,因此速度要快得多。如有疑问,请使用g++ -S并检查生成的汇编代码。

此外,内联代码可以更好地优化甚至矢量化(如另一个答案中所述),但当然,结果取决于您在编译中传递的编译器和确切标志。

于 2012-10-19T15:53:06.400 回答
2

您的问题的答案称为矢量化。比较 g++ 4.4.6 在编译时为您的代码的两个部分生成的汇编代码g++ -O3 -S

部分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 倍。

于 2012-10-19T15:53:17.677 回答