0

このテーマについて明確なベンチマークが見つからなかったので、作成しました。誰かが私のようにこれを探している場合に備えて、ここに投稿します。

質問が1つあります。SSEは、ループ内の4 fpu RSQRTよりも4倍高速であると想定されていませんか?高速ですが、わずか1.5倍です。SSEレジスタへの移行は、多くの計算を行わず、rsqrtのみを行うため、これほど大きな影響がありますか?または、SSE rsqrtの方がはるかに正確であるため、sse rsqrtの反復回数を確認するにはどうすればよいですか?2つの結果:

4 align16 float[4] RSQRT: 87011us 2236.07 - 2236.07 - 2236.07 - 2236.07
4 SSE align16 float[4]  RSQRT: 60008us 2236.07 - 2236.07 - 2236.07 - 2236.07

編集

/GS- /Gy /fp:fast /arch:SSE2 /Ox /Oy- /GL /OiAMD Athlon IIX2270でMSVC11を使用してコンパイル

テストコード:

#include <iostream>
#include <chrono>
#include <th/thutility.h>

int main(void)
{
    float i;
    //long i;
    float res;
    __declspec(align(16)) float var[4] = {0};

    auto t1 = std::chrono::high_resolution_clock::now();
    for(i = 0; i < 5000000; i+=1)
        res = sqrt(i);
    auto t2 = std::chrono::high_resolution_clock::now();
    std::cout << "1 float SQRT: " << std::chrono::duration_cast<std::chrono::microseconds>(t2-t1).count() << "us " << res << std::endl;

    t1 = std::chrono::high_resolution_clock::now();
    for(i = 0; i < 5000000; i+=1)
    {
         thutility::math::rsqrt(i, res);
         res *= i;
    }
    t2 = std::chrono::high_resolution_clock::now();
    std::cout << "1 float RSQRT: " << std::chrono::duration_cast<std::chrono::microseconds>(t2-t1).count() << "us " << res << std::endl;

    t1 = std::chrono::high_resolution_clock::now();
    for(i = 0; i < 5000000; i+=1)
    {
         thutility::math::rsqrt(i, var[0]);
         var[0] *= i;
    }
    t2 = std::chrono::high_resolution_clock::now();
    std::cout << "1 align16 float[4] RSQRT: " << std::chrono::duration_cast<std::chrono::microseconds>(t2-t1).count() << "us " <<  var[0] << std::endl;

    t1 = std::chrono::high_resolution_clock::now();
    for(i = 0; i < 5000000; i+=1)
    {
         thutility::math::rsqrt(i, var[0]);
         var[0] *= i;
         thutility::math::rsqrt(i, var[1]);
         var[1] *= i + 1;
         thutility::math::rsqrt(i, var[2]);
         var[2] *= i + 2;
    }
    t2 = std::chrono::high_resolution_clock::now();
    std::cout << "3 align16 float[4] RSQRT: "
        << std::chrono::duration_cast<std::chrono::microseconds>(t2-t1).count() << "us " 
        << var[0] << " - " << var[1] << " - " << var[2] << std::endl;

    t1 = std::chrono::high_resolution_clock::now();
    for(i = 0; i < 5000000; i+=1)
    {
         thutility::math::rsqrt(i, var[0]);
         var[0] *= i;
         thutility::math::rsqrt(i, var[1]);
         var[1] *= i + 1;
         thutility::math::rsqrt(i, var[2]);
         var[2] *= i + 2;
         thutility::math::rsqrt(i, var[3]);
         var[3] *= i + 3;
    }
    t2 = std::chrono::high_resolution_clock::now();
    std::cout << "4 align16 float[4] RSQRT: "
        << std::chrono::duration_cast<std::chrono::microseconds>(t2-t1).count() << "us " 
        << var[0] << " - " << var[1] << " - " << var[2] << " - " << var[3] << std::endl;

    t1 = std::chrono::high_resolution_clock::now();
    for(i = 0; i < 5000000; i+=1)
    {
        var[0] = i;
        __m128& cache = reinterpret_cast<__m128&>(var);
        __m128 mmsqrt = _mm_rsqrt_ss(cache);
        cache = _mm_mul_ss(cache, mmsqrt);
    }
    t2 = std::chrono::high_resolution_clock::now();
    std::cout << "1 SSE align16 float[4]  RSQRT: " << std::chrono::duration_cast<std::chrono::microseconds>(t2-t1).count()
        << "us " << var[0] << std::endl;

    t1 = std::chrono::high_resolution_clock::now();
    for(i = 0; i < 5000000; i+=1)
    {
        var[0] = i;
        var[1] = i + 1;
        var[2] = i + 2;
        var[3] = i + 3;
        __m128& cache = reinterpret_cast<__m128&>(var);
        __m128 mmsqrt = _mm_rsqrt_ps(cache);
        cache = _mm_mul_ps(cache, mmsqrt);
    }
    t2 = std::chrono::high_resolution_clock::now();
    std::cout << "4 SSE align16 float[4]  RSQRT: "
        << std::chrono::duration_cast<std::chrono::microseconds>(t2-t1).count() << "us " << var[0] << " - "
        << var[1] << " - " << var[2] << " - " << var[3] << std::endl;

    system("PAUSE");
}

フロートタイプを使用した結果:

1 float SQRT: 24996us 2236.07
1 float RSQRT: 28003us 2236.07
1 align16 float[4] RSQRT: 32004us 2236.07
3 align16 float[4] RSQRT: 51013us 2236.07 - 2236.07 - 5e+006
4 align16 float[4] RSQRT: 87011us 2236.07 - 2236.07 - 2236.07 - 2236.07
1 SSE align16 float[4]  RSQRT: 46999us 2236.07
4 SSE align16 float[4]  RSQRT: 60008us 2236.07 - 2236.07 - 2236.07 - 2236.07

私の結論は、4つ以上の変数で計算を行わない限り、SSE2を気にする価値がないということではありません。(おそらくこれはここではrsqrtにのみ適用されますが、コストのかかる計算であるため(複数の乗算も含まれます)、おそらく他の計算にも適用されます)

また、sqrt(x)は2回の反復でx * rsqrt(x)よりも高速であり、1回の反復でx * rsqrt(x)は距離計算には不正確すぎます。

したがって、いくつかのボードでx * rsqrt(x)がsqrt(x)よりも高速であるという記述は間違っています。したがって、1 / x ^(1/2)が直接必要でない限り、sqrtの代わりにrsqrtを使用することは論理的ではなく、精度を失う価値はありません。

SSE2フラグなしで試行しました(通常のrsqrtループにSSEを適用した場合、同じ結果が得られました)。

私のRSQRTは、地震rsqrtの修正された(同じ)バージョンです。

namespace thutility
{
    namespace math
    {
        void rsqrt(const float& number, float& res)
        {
              const float threehalfs = 1.5F;
              const float x2 = number * 0.5F;

              res = number;
              uint32_t& i = *reinterpret_cast<uint32_t *>(&res);    // evil floating point bit level hacking
              i  = 0x5f3759df - ( i >> 1 );                             // what the fuck?
              res = res * ( threehalfs - ( x2 * res * res ) );   // 1st iteration
              res = res * ( threehalfs - ( x2 * res * res ) );   // 2nd iteration, this can be removed
        }
    }
}
4

2 に答える 2

3

SSEコードで多くの不要なオーバーヘッドを取得するのは簡単です。

コードが効率的であることを確認したい場合は、コンパイラーの逆アセンブリーを調べてください。パフォーマンスを低下させることがよくある(そしてそれがあなたに影響を与える可能性があるように見える)1つのことは、メモリとSSEレジスタ間でデータを不必要に移動することです。

ループ内では、関連するすべてのデータと結果を、ではなくSSEレジスタに保持する必要がありますfloat[4]

メモリにアクセスしている限り、コンパイラが整列された移動命令を生成して、データをレジスタにロードするか、配列に書き戻すことを確認します。

また、生成されたSSE命令に、不要な移動命令やその他の断片が多く含まれていないことを確認してください。一部のコンパイラは、組み込み関数からSSEコードを生成するのがかなりひどいので、生成するコードを監視することにはお金がかかります。

最後に、CPUのマニュアル/仕様を参照して、使用するパックされた命令がスカラー命令と同じ速度で実際に実行されることを確認する必要があります。(最近のCPUの場合、そうすると思いますが、一部の古いCPUでは、パックされた命令に少なくとも少し追加の時間が必要でした。スカラーの4倍の長さではありませんが、4倍のスピードアップには到達できませんでした。 )。

于 2013-03-02T15:02:49.867 に答える
0

私の結論は、4つ以上の変数で計算を行わない限り、SSE2を気にする価値がないということではありません。(おそらくこれはここではrsqrtにのみ適用されますが、コストのかかる計算であるため(複数の乗算も含まれます)、おそらく他の計算にも適用されます)

また、sqrt(x)は2回の反復でx * rsqrt(x)よりも高速であり、1回の反復でx * rsqrt(x)は距離計算には不正確すぎます。

したがって、いくつかのボードでx * rsqrt(x)がsqrt(x)よりも高速であるという記述は間違っています。したがって、1 / x ^(1/2)が直接必要でない限り、sqrtの代わりにrsqrtを使用することは論理的ではなく、精度を失う価値はありません。

于 2013-03-02T15:02:15.370 に答える