【问题标题】:Using AVX intrinsics instead of SSE does not improve speed -- why?使用 AVX 内在函数而不是 SSE 并不能提高速度——为什么?
【发布时间】:2012-02-14 00:48:46
【问题描述】:

我使用 Intel 的 SSE 内在函数已经有一段时间了,性能得到了很好的提升。因此,我希望 AVX 内在函数能够进一步加速我的程序。不幸的是,直到现在情况并非如此。可能我犯了一个愚蠢的错误,所以如果有人能帮助我,我将非常感激。

我使用带有 g++ 4.6.1 的 Ubuntu 11.10。我用

编译了我的程序(见下文)
g++ simpleExample.cpp -O3 -march=native -o simpleExample

测试系统配备 Intel i7-2600 CPU。

这是说明我的问题的代码。在我的系统上,我得到了输出

98.715 ms, b[42] = 0.900038 // Naive
24.457 ms, b[42] = 0.900038 // SSE
24.646 ms, b[42] = 0.900038 // AVX

请注意,选择计算 sqrt(sqrt(sqrt(x))) 只是为了确保内存带宽不会限制执行速度;这只是一个例子。

simpleExample.cpp:

#include <immintrin.h>
#include <iostream>
#include <math.h> 
#include <sys/time.h>

using namespace std;

// -----------------------------------------------------------------------------
// This function returns the current time, expressed as seconds since the Epoch
// -----------------------------------------------------------------------------
double getCurrentTime(){
  struct timeval curr;
  struct timezone tz;
  gettimeofday(&curr, &tz);
  double tmp = static_cast<double>(curr.tv_sec) * static_cast<double>(1000000)
             + static_cast<double>(curr.tv_usec);
  return tmp*1e-6;
}

// -----------------------------------------------------------------------------
// Main routine
// -----------------------------------------------------------------------------
int main() {

  srand48(0);            // seed PRNG
  double e,s;            // timestamp variables
  float *a, *b;          // data pointers
  float *pA,*pB;         // work pointer
  __m128 rA,rB;          // variables for SSE
  __m256 rA_AVX, rB_AVX; // variables for AVX

  // define vector size 
  const int vector_size = 10000000;

  // allocate memory 
  a = (float*) _mm_malloc (vector_size*sizeof(float),32);
  b = (float*) _mm_malloc (vector_size*sizeof(float),32);

  // initialize vectors //
  for(int i=0;i<vector_size;i++) {
    a[i]=fabs(drand48());
    b[i]=0.0f;
  }

// +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
// Naive implementation
// +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
  s = getCurrentTime();
  for (int i=0; i<vector_size; i++){
    b[i] = sqrtf(sqrtf(sqrtf(a[i])));
  }
  e = getCurrentTime();
  cout << (e-s)*1000 << " ms" << ", b[42] = " << b[42] << endl;

// -----------------------------------------------------------------------------
  for(int i=0;i<vector_size;i++) {
    b[i]=0.0f;
  }
// -----------------------------------------------------------------------------

// +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
// SSE2 implementation
// +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
  pA = a; pB = b;

  s = getCurrentTime();
  for (int i=0; i<vector_size; i+=4){
    rA   = _mm_load_ps(pA);
    rB   = _mm_sqrt_ps(_mm_sqrt_ps(_mm_sqrt_ps(rA)));
    _mm_store_ps(pB,rB);
    pA += 4;
    pB += 4;
  }
  e = getCurrentTime();
  cout << (e-s)*1000 << " ms" << ", b[42] = " << b[42] << endl;

// -----------------------------------------------------------------------------
  for(int i=0;i<vector_size;i++) {
    b[i]=0.0f;
  }
// -----------------------------------------------------------------------------

// +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
// AVX implementation
// +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
  pA = a; pB = b;

  s = getCurrentTime();
  for (int i=0; i<vector_size; i+=8){
    rA_AVX   = _mm256_load_ps(pA);
    rB_AVX   = _mm256_sqrt_ps(_mm256_sqrt_ps(_mm256_sqrt_ps(rA_AVX)));
    _mm256_store_ps(pB,rB_AVX);
    pA += 8;
    pB += 8;
  }
  e = getCurrentTime();
  cout << (e-s)*1000 << " ms" << ", b[42] = " << b[42] << endl;

  _mm_free(a);
  _mm_free(b);

  return 0;
}

感谢任何帮助!

【问题讨论】:

    标签: c++ performance gcc sse avx


    【解决方案1】:

    这是因为在 Sandy Bridge 处理器上VSQRTPS(AVX 指令)占用的周期正好是SQRTPS(SSE 指令)的两倍。请参阅 Agner Fog 的优化指南:instruction tables,第 88 页。

    诸如平方根和除法之类的指令不会从 AVX 中受益。另一方面,加法、乘法等也可以。

    【讨论】:

      【解决方案2】:

      如果您对提高平方根性能感兴趣,可以使用 VRSQRTPS 和 Newton-Raphson 公式代替 VSQRTPS:

      x0 = vrsqrtps(a)
      x1 = 0.5 * x0 * (3 - (a * x0) * x0)
      

      VRSQRTPS 本身不能从 AVX 中受益,但其他计算可以。

      如果 23 位精度对您来说足够了,请使用它。

      【讨论】:

        【解决方案3】:

        只是为了完整性。仅当代码中的这些操作数量有限时,用于除法或平方根等操作的 Newton-Raphson (NR) 实现才会有益。这是因为如果您使用这些替代方法,您将对其他端口产生更大的压力,例如乘法和加法端口。这基本上就是为什么 x86 架构具有特殊的硬件单元来处理这些操作而不是替代软件解决方案(如 NR)的原因。我引用Intel 64 and IA-32 Architectures Optimization Reference Manual p.556:

        “在某些情况下,当除法或平方根运算是隐藏这些运算的某些延迟的较大算法的一部分时,使用 Newton-Raphson 进行近似会减慢执行速度。”

        所以在大型算法中使用 NR 时要小心。实际上,我的硕士论文是围绕这一点完成的,一旦发布,我将在此处留下链接以供将来参考。

        对于人们如何总是想知道某些指令的吞吐量和延迟,请查看IACA。它是英特尔提供的一个非常有用的工具,用于静态分析代码的内核执行性能。

        已编辑 这是论文的链接,有兴趣的人可以thesis

        【讨论】:

          【解决方案4】:

          根据您的处理器硬件,AVX 指令可以在硬件中模拟为 SSE 指令。您需要查看处理器的部件号以获得准确的规格,但这是低端和高端英特尔处理器之间的主要区别之一,即专用执行单元的数量与硬件仿真。

          【讨论】:

          • 我不知道 AVX 曾经被模拟过 - 你有这方面的参考吗?具体在哪些 CPU 上会出现这种情况?
          • 在 Sandy Bridge 上,根据instruction tables,第 87--88 页,VDIVPS/PD 似乎在端口 0 上执行了 2 个微操作,而 DIVPS/PS 执行了 1 个微操作。 SQRT 指令将类似。由于除法单元不是流水线的,因此执行时间要长 2 倍。这表明 Sandy Bridge 确实只有 128 位的除法单元实现。
          • @Norbert:感谢您的澄清 - 我不知道
          • @SoapBox:请以高端和低端处理器为例进行比较。
          • 根据 Intel 的 Interactive Intrinsics Guide,在 Broadwell 和更早版本上,rsqrtps 的 CPI(每指令周期数)为 1,而 vrsqrtps 的 CPI 为 2。在 Skylake 上,两者 的 CPI 均为 1,并且延迟降低。
          猜你喜欢
          • 2015-08-15
          • 2015-05-19
          • 2014-03-17
          • 2023-03-11
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2017-07-07
          • 1970-01-01
          相关资源
          最近更新 更多