2

ベンチマークを行っているところですfabsf()が、多くの場合、 よりも 10 倍遅いことがわかりましたfabs()。だから私はそれを逆アセンブルし、バージョンは命令doubleを使用していることがわかりましたが、バージョンはそうではありません。これは改善できますか?これは高速ですが、それほど多くはなく、機能しない可能性があるのではないかと心配しています。少し低レベルすぎます。fabsfloat

float mabs(float i)
{
    (*reinterpret_cast<MUINT32*>(&i)) &= 0x7fffffff;
    return i;
}

編集:コンパイラを忘れて申し訳ありません-私はまだ古き良きVS2005を使用しており、特別なライブラリはありません。

4

3 に答える 3

4

以下のコードを使用して、さまざまな可能性を簡単にテストできます。基本的に、素朴なテンプレート abs に対してビットフィドルをテストしますstd::abs。当然のことながら、素朴なテンプレート abs が勝ちます。うーん、意外と勝てる。私はstd::abs同じように速いと期待しています。実際には物事が遅くなることに注意してください-O3(少なくともcoliruでは)。

Coliru のホスト システムは、次のタイミングを示しています。

random number generation: 4240 ms
naive template abs: 190 ms
ugly bitfiddling abs: 241 ms
std::abs: 204 ms
::fabsf: 202 ms

Core i7 で GCC 4.9 を使用して Arch を実行している Virtualbox VM のこれらのタイミングは次のとおりです。

random number generation: 1453 ms
naive template abs: 73 ms
ugly bitfiddling abs: 97 ms
std::abs: 57 ms
::fabsf: 80 ms

MSVS2013 (Windows 7 x64) でのこれらのタイミング:

random number generation: 671 ms
naive template abs: 59 ms
ugly bitfiddling abs: 129 ms
std::abs: 109 ms
::fabsf: 109 ms

このベンチマーク コードで明らかな間違いを犯していない場合 (私を責めないでください。約 2 分でこれを書きました)、単に を使用するstd::absか、それが判明した場合はテンプレート バージョンを使用します。少し速くなります。


コード:

#include <algorithm>
#include <cmath>
#include <cstdint>
#include <cstdlib>
#include <chrono>
#include <iostream>
#include <random>
#include <vector>

#include <math.h>

using Clock = std::chrono::high_resolution_clock;
using milliseconds = std::chrono::milliseconds;

template<typename T>
T abs_template(T t)
{
  return t>0 ? t : -t;
}

float abs_ugly(float f)
{
  (*reinterpret_cast<std::uint32_t*>(&f)) &= 0x7fffffff;
  return f;
}

int main()
{
  std::random_device rd;
  std::mt19937 mersenne(rd());
  std::uniform_real_distribution<> dist(-std::numeric_limits<float>::lowest(), std::numeric_limits<float>::max());

  std::vector<float> v(100000000);

  Clock::time_point t0 = Clock::now();

  std::generate(std::begin(v), std::end(v), [&dist, &mersenne]() { return dist(mersenne); });

  Clock::time_point trand = Clock::now();

  volatile float temp;
  for (float f : v)
    temp = abs_template(f);

  Clock::time_point ttemplate = Clock::now();

  for (float f : v)
    temp = abs_ugly(f);

  Clock::time_point tugly = Clock::now();

  for (float f : v)
    temp = std::abs(f);

  Clock::time_point tstd = Clock::now();

  for (float f : v)
    temp = ::fabsf(f);

  Clock::time_point tfabsf = Clock::now();

  milliseconds random_time = std::chrono::duration_cast<milliseconds>(trand - t0);
  milliseconds template_time = std::chrono::duration_cast<milliseconds>(ttemplate - trand);
  milliseconds ugly_time = std::chrono::duration_cast<milliseconds>(tugly - ttemplate);
  milliseconds std_time = std::chrono::duration_cast<milliseconds>(tstd - tugly);
  milliseconds c_time = std::chrono::duration_cast<milliseconds>(tfabsf - tstd);
  std::cout << "random number generation: " << random_time.count() << " ms\n"
    << "naive template abs: " << template_time.count() << " ms\n"
    << "ugly bitfiddling abs: " << ugly_time.count() << " ms\n"
    << "std::abs: " << std_time.count() << " ms\n"
    << "::fabsf: " << c_time.count() << " ms\n";
}

ああ、あなたの実際の質問に答えるために:コンパイラがより効率的なコードを生成できない場合、特にこのような基本的な操作の場合、マイクロ最適化されたアセンブリを保存するより高速な方法があるとは思えません。

于 2014-05-05T15:01:01.253 に答える
3

ここでは多くのことが行われています。まず、x87 コプロセッサは SSE/AVX を支持して非推奨になっているため、コンパイラがまだこのfabs命令を使用していることを読んで驚いています。この質問にベンチマークの回答を投稿した他の人が、SSE をサポートするプラットフォームを使用している可能性は十分にあります。結果は大きく異なる場合があります。

fabsコンパイラがとに異なるロジックを使用する理由がわかりませんfabsffloataを x87 スタックにロードし、そのfabs命令を同じように簡単に使用することは完全に可能です。コンパイラのサポートなしでこれを自分で再現する際の問題は、操作をコンパイラの通常の最適化パイプラインに統合できないことです。「この float をロードし、fabs命令を使用し、この float をメモリに返す」と言うと、コンパイラはまさにそれを行います...そして、すでに処理する準備ができているフロートをメモリに戻し、ロードし直し、fabs命令を使用し、メモリに戻し、x87スタックに再度ロードして再開する必要がある場合があります通常の最適化可能なパイプライン。fabs.

つまり、浮動小数点演算の統合コンパイラ サポートに勝るものはありません。このサポートがない場合、インライン アセンブラは、おそらく現在よりもさらに遅くなる可能性があります。あなたがする最も速いことは、floatfabsで関数の代わりに関数を使用することかもしれません。fabsf

参考までに、最新のコンパイラと最新のプラットフォームは、SSE 命令andps(float の場合) とandpd(double の場合) を使用して、ビット記号の AND をとります。どちらも同じくらい速いです。最新のコンパイラは、コンパイラ組み込み関数を必要とせずに、 / のようなパターンを検出x < 0 ? -x : xして最適なandps/命令を生成することもあります。andpd

于 2014-05-05T15:33:05.647 に答える
2

std::absのオーバーロードを試しましたfloatか? それが標準的な C++ の方法です。

また、余談ですが、ビット変更バージョンは厳密なエイリアシング規則に違反していることに注意する必要があります (intfloatが同じサイズであるというより基本的な仮定に加えて) ため、未定義の動作になります。

于 2014-05-05T14:22:02.577 に答える