打败编译器

时间:2012-10-14 18:22:43

标签: c++ intel intrinsics avx

我试图使用英特尔内在函数来击败编译器优化代码。有时候我可以做到,有时候我可以做到。

我想问题是,为什么我有时会击败编译器,但有时候不是?我使用英特尔内在函数获得了operator+=以下0.006秒的时间(使用裸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剖析器告诉我我的矢量算术运算可以使用一些调整。

enter image description here

1 个答案:

答案 0 :(得分:5)

如Mysticial所述,工会黑客是test2中最可能的罪魁祸首。它强制数据通过L1缓存,虽然速度很快,但有一些延迟远远大于矢量代码提供的2个周期的增益(见下文)。

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

test1不同,常量向量可以存储在xmm寄存器中,在这种情况下,循环只包含add指令。但是代码并不像预期的那样快3倍。原因是流水线指令:每个添加指令有延迟3个周期,但CPU可以在每个周期彼此独立时启动一个新指令。这是每分量矢量加法的情况。因此,向量代码在每个循环迭代中执行一个添加指令,具有3个循环延迟,并且标量代码执行3个添加指令,仅执行5个循环(1个启动/循环,第3个具有延迟3:2 + 3 = 5)。 / p>

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