4

インテルの組み込み関数を使用して、コンパイラーに最適化されたコードを打ち負かそうとしています。できることもあればできないこともあります。

問題は、なぜコンパイラを打ち負かすことができるのか、そうでないのかということだと思います。インテルの組み込み関数を使用した場合は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 には p0 ~ p5 の 6 つの実行ユニットがあり、浮動小数点の乗算/除算は p0 で実行され、浮動小数点の加算と整数の乗算は p1 で実行されます。また、除算は乗算/加算の 3 ~ 4 倍のサイクルを要し、パイプライン化されません (つまり、除算の実行中に実行ユニットは別の命令を開始できません)。そのtest2ため、ベクトル コードがユニット p0 でコストのかかる除算といくつかの乗算が終了するのを待っている間、スカラー コードは p1 で追加の 2 つの加算命令を実行できます。これにより、ベクトル命令の利点が失われる可能性が高くなります。

test1が異なる場合、定数ベクトルをレジスタに格納できますxmm。その場合、ループには add 命令のみが含まれます。しかし、コードは予想されるほど 3 倍高速ではありません。その理由はパイプライン化された命令にあります。各追加命令には 3 サイクルのレイテンシがありますが、CPU は互いに独立している場合、サイクルごとに新しい命令を開始できます。これは、コンポーネントごとのベクトル加算の場合です。したがって、ベクトル コードは 3 サイクルのレイテンシでループ反復ごとに 1 つの加算命令を実行し、スカラー コードは 3 つの加算命令を実行し、わずか 5 サイクルしかかかりません (1 開始/サイクル、3 番目のレイテンシは 3: 2 + 3 = 5)。

CPU アーキテクチャと最適化に関する非常に優れたリソースは、http: //www.agner.org/optimize/ です。

于 2012-10-15T00:05:03.983 に答える