現代のハードウェアにおける浮動小数点と整数の計算 質問する

現代のハードウェアにおける浮動小数点と整数の計算 質問する

私は C++ でパフォーマンスが重要な作業を行っていますが、現在、本質的に浮動小数点である問題に対して、「より高速」という理由で整数計算を使用しています。これにより、多くの厄介な問題が発生し、多くの厄介なコードが追加されます。

さて、私は、およそ 386 日頃に浮動小数点計算が非常に遅いという記事を読んだことを覚えています。その頃は (記憶が正しければ) オプションのコプロセッサがあったと思います。しかし、今日では CPU が飛躍的に複雑で強力になっているので、浮動小数点計算をしても整数計算をしても「速度」に違いはありません。特に、実際の計算時間は、パイプラインの停止やメイン メモリからの取得などに比べればごくわずかですから。

正しい答えは、ターゲット ハードウェアでベンチマークを行うことだとわかっていますが、これをテストする良い方法は何でしょうか。 2 つの小さな C++ プログラムを作成し、その実行時間を Linux 上の「時間」と比較しましたが、実際の実行時間は変動が大きすぎます (仮想サーバー上で実行しているので役に立ちません)。 何百ものベンチマークを実行したり、グラフを作成したりするのに 1 日中費やす以外に、相対速度を合理的にテストする方法はありますか。 何かアイデアや考えはありますか。 私が完全に間違っているのでしょうか。

私が使用したプログラムは次のとおりですが、それらは決して同一ではありません。

#include <iostream>
#include <cmath>
#include <cstdlib>
#include <time.h>

int main( int argc, char** argv )
{
    int accum = 0;

    srand( time( NULL ) );

    for( unsigned int i = 0; i < 100000000; ++i )
    {
        accum += rand( ) % 365;
    }
    std::cout << accum << std::endl;

    return 0;
}

プログラム2:

#include <iostream>
#include <cmath>
#include <cstdlib>
#include <time.h>

int main( int argc, char** argv )
{

    float accum = 0;
    srand( time( NULL ) );

    for( unsigned int i = 0; i < 100000000; ++i )
    {
        accum += (float)( rand( ) % 365 );
    }
    std::cout << accum << std::endl;

    return 0;
}

編集: 私が関心を持っているプラ​​ットフォームは、デスクトップ Linux および Windows マシン上で実行される通常の x86 または x86-64 です。

編集 2 (以下のコメントから貼り付け): 現在、広範なコード ベースがあります。実際、私は「整数計算の方が高速なので float を使用してはならない」という一般論に遭遇しました。そして、この一般論を反証する方法 (これが真実であるかどうかは別として) を探しています。すべての作業を実行してからプロファイリングしない限り、正確な結果を予測することは不可能だと認識しています。

とにかく、素晴らしい回答と助言をありがとうございました。他に何かあれば遠慮なく追加してください :)。

ベストアンサー1

例えば(数字が小さいほど速い)、

64 ビット Intel Xeon X5550 @ 2.67GHz、gcc 4.1.2-O3

short add/sub: 1.005460 [0]
short mul/div: 3.926543 [0]
long add/sub: 0.000000 [0]
long mul/div: 7.378581 [0]
long long add/sub: 0.000000 [0]
long long mul/div: 7.378593 [0]
float add/sub: 0.993583 [0]
float mul/div: 1.821565 [0]
double add/sub: 0.993884 [0]
double mul/div: 1.988664 [0]

32 ビット デュアル コア AMD Opteron(tm) プロセッサ 265 @ 1.81GHz、gcc 3.4.6-O3

short add/sub: 0.553863 [0]
short mul/div: 12.509163 [0]
long add/sub: 0.556912 [0]
long mul/div: 12.748019 [0]
long long add/sub: 5.298999 [0]
long long mul/div: 20.461186 [0]
float add/sub: 2.688253 [0]
float mul/div: 4.683886 [0]
double add/sub: 2.700834 [0]
double mul/div: 4.646755 [0]

としてダンは指摘したクロック周波数を正規化した後でも(パイプライン設計ではそれ自体が誤解を招く可能性があります)、結果はCPUアーキテクチャによって大きく異なります(個人アルミニウム/FPUパフォーマンス同様に実際のALU/FPUの数コアあたり利用可能スーパースカラーデザインは、独立した操作を並行して実行できる-- 以下のすべての操作は順番に依存しているため、後者の要素は以下のコードでは実行されません。

貧乏人の FPU/ALU 操作ベンチマーク:

#include <stdio.h>
#ifdef _WIN32
#include <sys/timeb.h>
#else
#include <sys/time.h>
#endif
#include <time.h>
#include <cstdlib>

double
mygettime(void) {
# ifdef _WIN32
  struct _timeb tb;
  _ftime(&tb);
  return (double)tb.time + (0.001 * (double)tb.millitm);
# else
  struct timeval tv;
  if(gettimeofday(&tv, 0) < 0) {
    perror("oops");
  }
  return (double)tv.tv_sec + (0.000001 * (double)tv.tv_usec);
# endif
}

template< typename Type >
void my_test(const char* name) {
  Type v  = 0;
  // Do not use constants or repeating values
  //  to avoid loop unroll optimizations.
  // All values >0 to avoid division by 0
  // Perform ten ops/iteration to reduce
  //  impact of ++i below on measurements
  Type v0 = (Type)(rand() % 256)/16 + 1;
  Type v1 = (Type)(rand() % 256)/16 + 1;
  Type v2 = (Type)(rand() % 256)/16 + 1;
  Type v3 = (Type)(rand() % 256)/16 + 1;
  Type v4 = (Type)(rand() % 256)/16 + 1;
  Type v5 = (Type)(rand() % 256)/16 + 1;
  Type v6 = (Type)(rand() % 256)/16 + 1;
  Type v7 = (Type)(rand() % 256)/16 + 1;
  Type v8 = (Type)(rand() % 256)/16 + 1;
  Type v9 = (Type)(rand() % 256)/16 + 1;

  double t1 = mygettime();
  for (size_t i = 0; i < 100000000; ++i) {
    v += v0;
    v -= v1;
    v += v2;
    v -= v3;
    v += v4;
    v -= v5;
    v += v6;
    v -= v7;
    v += v8;
    v -= v9;
  }
  // Pretend we make use of v so compiler doesn't optimize out
  //  the loop completely
  printf("%s add/sub: %f [%d]\n", name, mygettime() - t1, (int)v&1);
  t1 = mygettime();
  for (size_t i = 0; i < 100000000; ++i) {
    v /= v0;
    v *= v1;
    v /= v2;
    v *= v3;
    v /= v4;
    v *= v5;
    v /= v6;
    v *= v7;
    v /= v8;
    v *= v9;
  }
  // Pretend we make use of v so compiler doesn't optimize out
  //  the loop completely
  printf("%s mul/div: %f [%d]\n", name, mygettime() - t1, (int)v&1);
}

int main() {
  my_test< short >("short");
  my_test< long >("long");
  my_test< long long >("long long");
  my_test< float >("float");
  my_test< double >("double");

  return 0;
}

おすすめ記事