ฉันได้จัดทำโปรไฟล์หลักคณิตศาสตร์ของเราใน Intel Core Duo และในขณะที่ดูวิธีการต่างๆของสแควร์รูทฉันสังเกตเห็นว่ามีอะไรแปลก ๆ : การใช้การดำเนินการสเกลาร์ SSE การใช้สแควร์รูทซึ่งกันและกันเร็วกว่าและคูณ เพื่อรับ sqrt มากกว่าที่จะใช้ opcode sqrt ดั้งเดิม!
ฉันกำลังทดสอบด้วยการวนซ้ำเช่น:
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() );
}
ฉันได้ลองสิ่งนี้กับร่างกายที่แตกต่างกันสองสามอย่างสำหรับ TestSqrtFunction และฉันมีเวลาบางอย่างที่ทำให้หัวของฉันเกา สิ่งที่เลวร้ายที่สุดคือการใช้ฟังก์ชัน sqrt () แบบเนทีฟและปล่อยให้คอมไพเลอร์ "สมาร์ท" เพิ่มประสิทธิภาพ " ที่ 24ns / float โดยใช้ x87 FPU สิ่งนี้ไม่ดีอย่างน่าสมเพช:
inline float TestSqrtFunction( float in )
{ return sqrt(in); }
สิ่งต่อไปที่ฉันลองใช้คือการใช้ภายในเพื่อบังคับให้คอมไพเลอร์ใช้สเกลาร์ sqrt opcode ของ 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
}
ดีกว่านี้ที่ 11.9ns / float ฉันยังลองใช้เทคนิคการประมาณค่า Newton-Raphson ที่แปลกประหลาดของ Carmackซึ่งทำงานได้ดีกว่าฮาร์ดแวร์ที่ 4.3ns / float แม้ว่าจะมีข้อผิดพลาด 1 ใน 2 10 (ซึ่งมากเกินไปสำหรับวัตถุประสงค์ของฉัน)
doozy คือตอนที่ฉันลอง SSE op สำหรับสแควร์รูทซึ่งกันและกันจากนั้นใช้การคูณเพื่อรับสแควร์รูท (x * 1 / √x = √x) แม้ว่าจะใช้เวลาดำเนินการสองอย่าง แต่ก็เป็นวิธีแก้ปัญหาที่เร็วที่สุดที่ 1.24ns / float และแม่นยำถึง 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
}
คำถามของฉันคืออะไรให้ ? เหตุใด opcode สแควร์รูทในตัวของ SSE จึงช้ากว่าการสังเคราะห์จากการคำนวณทางคณิตศาสตร์อื่น ๆ อีกสองรายการ
ฉันแน่ใจว่านี่เป็นต้นทุนของ op จริงๆเพราะฉันได้ตรวจสอบแล้ว:
- ข้อมูลทั้งหมดอยู่ในแคชและการเข้าถึงเป็นไปตามลำดับ
- ฟังก์ชันจะอยู่ในบรรทัด
- การคลายการวนซ้ำไม่ทำให้เกิดความแตกต่าง
- แฟล็กคอมไพเลอร์ถูกตั้งค่าเป็นการปรับให้เหมาะสมเต็มรูปแบบ (และการประกอบเป็นสิ่งที่ดีฉันตรวจสอบแล้ว)
( แก้ไข : stephentyrone ชี้ให้เห็นอย่างถูกต้องว่าการดำเนินการกับสตริงตัวเลขที่ยาวควรใช้ตัวดำเนินการบรรจุ SIMD แบบ vectorizing เช่นrsqrtps
- แต่โครงสร้างข้อมูลอาร์เรย์ที่นี่มีไว้เพื่อการทดสอบเท่านั้นสิ่งที่ฉันพยายามวัดคือประสิทธิภาพสเกลาร์สำหรับใช้ในโค้ด ที่ไม่สามารถเป็นเวกเตอร์ได้)