このテーマについて明確なベンチマークが見つからなかったので、作成しました。誰かが私のようにこれを探している場合に備えて、ここに投稿します。
質問が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 /Oi
AMD 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
}
}
}