web-dev-qa-db-ja.com

SSEの代わりにAVX組み込み関数を使用しても速度は向上しません-なぜですか?

私はかなりの時間、IntelのSSE組み込み関数を使用してパフォーマンスを向上させてきました。したがって、AVX組み込み関数がプログラムをさらに高速化することを期待していました。おそらく私は愚かな間違いをしているので、誰かが私を助けてくれたらとても感謝しています。

Ubuntu 11.10とg ++ 4.6.1を使用しています。プログラムをコンパイルしました(以下を参照)

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;
}

どんな助けも大歓迎です!

44
user1158218

これは、Sandy BridgeプロセッサでVSQRTPS(AVX命令)がSQRTPS(SSE命令)の正確に2倍のサイクルを要するためです。 Agner Fogの最適化ガイドを参照してください: 命令表 、88ページ。

平方根や除算などの命令はAVXの恩恵を受けません。一方、加算、乗算などは行います。

43
Norbert P.

VSQRTPSの代わりに平方根のパフォーマンスを向上させることに関心がある場合は、VRSQRTPSとニュートンラプソンの式を使用できます。

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

VRSQRTPS自体はAVXの恩恵を受けませんが、他の計算は恩恵を受けます。

23ビットの精度で十分な場合に使用します。

10
Evgeny Kluev

完全を期すためだけに。除算や平方根などの演算のニュートンラプソン(NR)実装は、コード内の演算の数が限られている場合にのみ有益です。これは、これらの代替方法を使用した場合、乗算ポートや加算ポートなどの他のポートにより多くの圧力がかかるためです。これが基本的に、x86アーキテクチャに、代替ソフトウェアソリューション(NRなど)の代わりにこれらの操作を処理する特別なハードウェアユニットがある理由です。 Intel 64およびIA-32アーキテクチャ最適化リファレンスマニュアル p.556から引用:

「場合によっては、除算または平方根演算が、これらの演算のレイテンシの一部を隠す大きなアルゴリズムの一部である場合、Newton-Raphsonでの近似により実行が遅くなる可能性があります。」

したがって、大きなアルゴリズムでNRを使用する場合は注意してください。実際、この点について修士論文がありましたので、今後の参考のためにここにリンクを残します。

また、特定の命令のスループットとレイテンシについて常に疑問に思っている人のために、 [〜#〜] iaca [〜#〜] をご覧ください。コードのコア内実行パフォーマンスを静的に分析するために、Intelが提供する非常に便利なツールです。

編集済みここに興味がある人のための論文へのリンクがあります 論文

7
Salah Saleh

プロセッサのハードウェアによっては、AVX命令がハードウェアでSSE命令としてエミュレートされる場合があります。正確な仕様を取得するにはプロセッサの部品番号を調べる必要がありますが、これは1つです。ローエンドとハイエンドのインテルプロセッサの主な違い、特殊化された実行ユニットの数とハードウェアエミュレーションの違い。

6
SoapBox