Saya telah membuat profil beberapa matematika inti kami pada Intel Core Duo, dan ketika melihat berbagai pendekatan untuk akar kuadrat, saya telah melihat sesuatu yang aneh: menggunakan operasi skalar SSE, lebih cepat mengambil akar kuadrat timbal balik dan mengalikannya untuk mendapatkan sqrt, daripada menggunakan opcode sqrt asli!
Saya mengujinya dengan loop seperti:
inline float TestSqrtFunction( float in );
void TestFunc()
{
#define ARRAYSIZE 4096
#define NUMITERS 16386
float flIn[ ARRAYSIZE ]; // filled with random numbers ( 0 .. 2^22 )
float flOut [ ARRAYSIZE ]; // filled with 0 to force fetch into L1 cache
cyclecounter.Start();
for ( int i = 0 ; i < NUMITERS ; ++i )
for ( int j = 0 ; j < ARRAYSIZE ; ++j )
{
flOut[j] = TestSqrtFunction( flIn[j] );
// unrolling this loop makes no difference -- I tested it.
}
cyclecounter.Stop();
printf( "%d loops over %d floats took %.3f milliseconds",
NUMITERS, ARRAYSIZE, cyclecounter.Milliseconds() );
}
Saya sudah mencoba ini dengan beberapa badan berbeda untuk TestSqrtFunction, dan saya punya beberapa pengaturan waktu yang benar-benar menggaruk kepala saya. Yang terburuk dari semuanya sejauh ini adalah menggunakan fungsi sqrt () asli dan membiarkan kompiler "pintar" "mengoptimalkan". Pada 24ns / float, menggunakan x87 FPU, ini sangat buruk:
inline float TestSqrtFunction( float in )
{ return sqrt(in); }
Hal berikutnya yang saya coba adalah menggunakan intrinsik untuk memaksa kompiler menggunakan opcode sqrt skalar SSE:
inline void SSESqrt( float * restrict pOut, float * restrict pIn )
{
_mm_store_ss( pOut, _mm_sqrt_ss( _mm_load_ss( pIn ) ) );
// compiles to movss, sqrtss, movss
}
Ini lebih baik, pada 11.9ns / float. Saya juga mencoba teknik perkiraan Newton-Raphson yang aneh dari Carmack , yang berjalan bahkan lebih baik daripada perangkat kerasnya, pada 4.3ns / float, meskipun dengan kesalahan 1 dalam 2 10 (yang terlalu berlebihan untuk tujuan saya).
Doozy adalah ketika saya mencoba operasi SSE untuk akar kuadrat timbal balik , dan kemudian menggunakan perkalian untuk mendapatkan akar kuadrat (x * 1 / √x = √x). Meskipun ini membutuhkan dua operasi yang bergantung, ini adalah solusi tercepat sejauh ini, pada 1,24ns / float dan akurat hingga 2 -14 :
inline void SSESqrt_Recip_Times_X( float * restrict pOut, float * restrict pIn )
{
__m128 in = _mm_load_ss( pIn );
_mm_store_ss( pOut, _mm_mul_ss( in, _mm_rsqrt_ss( in ) ) );
// compiles to movss, movaps, rsqrtss, mulss, movss
}
Pertanyaan saya pada dasarnya adalah apa yang memberi ? Mengapa opcode akar kuadrat bawaan ke perangkat keras SSE lebih lambat daripada menyintesisnya dari dua operasi matematika lainnya?
Saya yakin ini benar-benar biaya operasi itu sendiri, karena saya telah memverifikasi:
- Semua data cocok dengan cache, dan aksesnya berurutan
- fungsinya sebaris
- membuka gulungan loop tidak ada bedanya
- bendera kompiler disetel ke optimasi penuh (dan perakitannya bagus, saya centang)
( edit : stephentyrone dengan benar menunjukkan bahwa operasi pada string angka yang panjang harus menggunakan operasi yang dikemas SIMD vektor, seperti rsqrtps
- tetapi struktur data array di sini hanya untuk tujuan pengujian: apa yang sebenarnya saya coba ukur adalah kinerja skalar untuk digunakan dalam kode yang tidak dapat divektorisasi.)
inline float SSESqrt( float restrict fIn ) { float fOut; _mm_store_ss( &fOut, _mm_sqrt_ss( _mm_load_ss( &fIn ) ) ); return fOut; }
. Tetapi ini adalah ide yang buruk karena dapat dengan mudah menyebabkan kemacetan pemuatan-hit-store jika CPU menulis float ke stack dan kemudian segera membacanya kembali - beralih dari register vektor ke register float untuk nilai kembalian khususnya adalah berita buruk. Selain itu, opcode mesin yang mendasari yang diwakili oleh SSE intrinsik take address operand.
eax
) sangat buruk, sementara perjalanan bolak-balik antara xmm0 dan stack dan tidak kembali, karena penerusan toko Intel. Anda bisa mengatur waktunya sendiri untuk memastikannya. Umumnya cara termudah untuk melihat potensi LHS adalah dengan melihat rakitan yang dipancarkan dan melihat di mana data disulap di antara set register; kompiler Anda mungkin melakukan hal yang cerdas, atau mungkin juga tidak. Untuk menormalkan vektor, saya menulis hasil saya di sini: bit.ly/9W5zoU