syghの新フラグメント置き場

プログラミングTipsやコード断片の保管場所です。お絵描きもときどき載せます。

不定値IND・非数NaNと浮動小数点演算速度

コードの単体パフォーマンステストをやっていて興味深い現象に遭遇しました。
演算途中で発生した不定値 IND (indeterminate value, -1.#IND) や、非数 NaN (not a number, 1.#QNAN) をそのまま計算に使い続けると、環境によっては正常値で演算する場合と比べて処理速度が異様に遅くなるようです。
確認したコンパイラはVisual C++ 2012。
/fp:precise でも /fp:fast でも同様。
実際に現象を確認したコードはもっと複雑な3次元画像処理なのですが、下記のような簡単なテストプログラムでも再現できました。Intel 環境(Xeon W3520)で64bitネイティブコードを実行した場合、1億回繰り返すと30倍を超える演算速度の差が出ます。しかしさらに興味深いのは、AMD 環境(Athlon 64 X2 5600+)だとむしろ正常値よりも IND/NaN を使い続けたほうが高速になったことです(もちろん最終的な演算結果に意味はありませんが)。

なお、sqrt() に -1 などの負数を入力することによる直接の速度低下はないようです(当然演算結果は不正ですが)。詳しく分析してはいませんが、分岐における IND/NaN との比較や、sqrt() への IND/NaN の直接入力あたりがIntelプロセッサにおけるパフォーマンスを低下させているものと思われます。

やはり数値計算におけるセオリー通り、入力値の事前チェックを行なって、数学的に意味をなさない演算は最初から実行しないようにするべきでしょう。IND/NaN の存在は、コードのFLOPSを見るだけの単純なパフォーマンステストにも影響を及ぼすので注意が必要です(計測の際に実データとは異なるゼロクリアされたダミーデータを渡すなどすると、実データの場合と大きく異なる処理時間になる可能性があります)。
場合によっては _isnan(), _isnanf() などで適宜演算結果を非数判定することも検討したほうがよいと思います。

#include <cstdio>
#include <iostream>
#include <string>
#include <chrono>

#include <conio.h>

int main()
{
  std::cout << "Value? = ";

  std::string buf;
  std::getline(std::cin, buf);

  const double indeterminateVal = sqrt(-1);

  const double nanVal = std::numeric_limits<double>::quiet_NaN();

  const double inputVal =
    (_strcmpi(buf.c_str(), "IND") == 0)
    ? indeterminateVal
    : ((_strcmpi(buf.c_str(), "NAN") == 0) ? nanVal : strtod(buf.c_str(), nullptr));

  std::cout << inputVal << std::endl;

  const auto startTime = std::chrono::steady_clock::now();

  double total1 = 0, total2 = 0;

  for (int i = 0; i < 100 * 1000 * 1000; ++i)
  {
    if (inputVal < i)
    {
      total1 += sqrt(inputVal);
    }
    else
    {
      total2 += sqrt(inputVal);
    }
  }

  // コンパイラ最適化によるコード消去の防止のためになんらかの参照が必要。
  printf("Total = %f, %f\n", total1, total2);

  const auto endTime = std::chrono::steady_clock::now();

  std::cout << "Elapsed time = " << std::chrono::duration_cast<std::chrono::milliseconds>(endTime - startTime).count() << "[ms]" << std::endl;

  puts("Press any...");
  _getch();
  return 0;
}