4

我正在尝试使用英特尔内在函数来击败编译器优化代码。有时我能做到,有时我做不到。

我想问题是,为什么有时我可以击败编译器,但有时却不行?使用 Intel 内在函数时,我得到了 0.006 秒的时间operator+=(相对于使用纯 C++ 时的 0.009 秒),但operator+使用内在函数的时间为 0.07 秒,而纯 C++ 只有 0.03 秒。

#include <windows.h>
#include <stdio.h>
#include <intrin.h>

class Timer
{
  LARGE_INTEGER startTime ;
  double fFreq ;

public:
  Timer() {
    LARGE_INTEGER freq ;
    QueryPerformanceFrequency( &freq ) ;
    fFreq = (double)freq.QuadPart ;
    reset();
  }

  void reset() {   QueryPerformanceCounter( &startTime ) ;  }

  double getTime() {
    LARGE_INTEGER endTime ;
    QueryPerformanceCounter( &endTime ) ;
    return ( endTime.QuadPart - startTime.QuadPart ) / fFreq ; // as double
  }
} ;


inline float randFloat(){
  return (float)rand()/RAND_MAX ;
}



// Use my optimized code,
#define OPTIMIZED_PLUS_EQUALS
#define OPTIMIZED_PLUS

union Vector
{
  struct { float x,y,z,w ; } ;
  __m128 reg ;

  Vector():x(0.f),y(0.f),z(0.f),w(0.f) {}
  Vector( float ix, float iy, float iz, float iw ):x(ix),y(iy),z(iz),w(iw) {}
  //Vector( __m128 val ):x(val.m128_f32[0]),y(val.m128_f32[1]),z(val.m128_f32[2]),w(val.m128_f32[3]) {}
  Vector( __m128 val ):reg( val ) {} // 2x speed, above

  inline Vector& operator+=( const Vector& o ) {
    #ifdef OPTIMIZED_PLUS_EQUALS
    // YES! I beat it!  Using this intrinsic is faster than just C++.
    reg = _mm_add_ps( reg, o.reg ) ;
    #else
    x+=o.x, y+=o.y, z+=o.z, w+=o.w ;
    #endif
    return *this ;
  }

  inline Vector operator+( const Vector& o )
  {
    #ifdef OPTIMIZED_PLUS
    // This is slower
    return Vector( _mm_add_ps( reg, o.reg ) ) ;
    #else
    return Vector( x+o.x, y+o.y, z+o.z, w+o.w ) ;
    #endif
  }

  static Vector random(){
    return Vector( randFloat(), randFloat(), randFloat(), randFloat() ) ;
  }

  void print() {

    printf( "%.2f %.2f %.2f\n", x,y,z,w ) ;
  }
} ;

int runs = 8000000 ;
Vector sum ;

// OPTIMIZED_PLUS_EQUALS (intrinsics) runs FASTER 0.006 intrinsics, vs 0.009 (std C++)
void test1(){
  for( int i = 0 ; i < runs ; i++ )
    sum += Vector(1.f,0.25f,0.5f,0.5f) ;//Vector::random() ;
}

// OPTIMIZED* runs SLOWER (0.03 for reg.C++, vs 0.07 for intrinsics)
void test2(){
  float j = 27.f ;
  for( int i = 0 ; i < runs ; i++ )
  {
    sum += Vector( j*i, i, i/j, i ) + Vector( i, 2*i*j, 3*i*j*j, 4*i ) ;
  }
}

int main()
{
  Timer timer ;

  //test1() ;
  test2() ;

  printf( "Time: %f\n", timer.getTime() ) ;
  sum.print() ;

}

编辑

我为什么要这样做?VS 2012 分析器告诉我我的向量算术运算可以使用一些调整。

在此处输入图像描述

4

1 回答 1

5

正如 Mysticial 所指出的,工会黑客是test2. 它强制数据通过 L1 缓存,虽然速度很快,但有一些延迟,远远超过向量代码提供的 2 个周期的增益(见下文)。

但也要考虑到 CPU 可以乱序并行运行多条指令(超标量 CPU)。例如,Sandy Bridge 有 6 个执行单元,p0--p5,浮点乘法/除法运行在 p0,浮点加法和整数乘法运行在 p1。此外,除法比乘法/加法多花费 3-4 倍的周期,并且不是流水线的(即执行单元在执行除法时无法启动另一条指令)。所以在 中test2,当向量代码在单元 p0 上等待昂贵的除法和一些乘法完成时,标量代码可以在 p1 上执行额外的 2 条加法指令,这很可能会消除向量指令的任何优势。

test1不同的是,常数向量可以存储在xmm寄存器中,在这种情况下,循环只包含加法指令。但是代码并没有像预期的那样快 3 倍。原因是流水线指令:每条加法指令都有 3 个周期的延迟,但是当它们相互独立时,CPU 可以在每个周期开始一个新的指令。这是每分量向量加法的情况。因此,向量代码在每个循环迭代中执行一条加法指令,延迟时间为 3 次,而标量代码执行 3 条加法指令,仅花费 5 个周期(1 个启动/周期,第 3 个延迟为 3:2 + 3 = 5)。

关于 CPU 架构和优化的一个非常好的资源是http://www.agner.org/optimize/

于 2012-10-15T00:05:03.983 回答