मैं एक इंटेल कोर डुओ पर हमारे कुछ कोर गणित की रूपरेखा तैयार कर रहा हूं, और वर्गमूल के विभिन्न दृष्टिकोणों को देखते हुए मैंने कुछ अजीब देखा है: SSE स्केलर ऑपरेशन का उपयोग करते हुए, पारस्परिक वर्गमूल लेना और इसे गुणा करना अधिक तेज़ है sqrt पाने के लिए, देशी sqrt opcode का उपयोग करना है!
मैं इसे एक लूप के साथ परीक्षण कर रहा हूँ जैसे:
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 / फ्लोट पर, x87 FPU का उपयोग करके यह pathetically खराब था:
inline float TestSqrtFunction( float in )
{ return sqrt(in); }
अगली चीज़ जो मैंने कोशिश की, वह एक आंतरिक का उपयोग करके एसएसई के स्केलर स्कार्ट ओपोड का उपयोग करने के लिए मजबूर करने के लिए किया गया था:
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 / फ्लोट पर। मैंने कार्मैक की निराला न्यूटन-राफसन सन्निकटन तकनीक की भी कोशिश की , जो कि हार्डवेयर से भी बेहतर, 4.3ns / फ्लोट पर चलती थी, हालाँकि 1 से 2 10 की त्रुटि के साथ (जो कि मेरे उद्देश्यों के लिए बहुत अधिक है)।
जब मैं पारस्परिक वर्गमूल के लिए SSE सेशन करने की कोशिश करता था, तब डोज़ी होता था , और तब वर्गमूल (x * 1 / √x = )x) प्राप्त करने के लिए एक बहुतायत से उपयोग किया जाता था। भले ही यह दो आश्रित परिचालनों में लेता हो, लेकिन यह 1.24ns / फ्लोट पर और 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
}
मेरा प्रश्न मूल रूप से क्या है ? SSE का अंतर्निहित हार्डवेयर वर्ग रूट opcode दो अन्य गणित कार्यों से बाहर निकलने की तुलना में धीमा क्यों है ?
मुझे यकीन है कि यह वास्तव में स्वयं की लागत है, क्योंकि मैंने सत्यापित किया है:
- सभी डेटा कैश में फिट होते हैं, और एक्सेस अनुक्रमिक होते हैं
- कार्य इनबिल्ट हैं
- पाश को नियंत्रित करने से कोई फर्क नहीं पड़ता
- कंपाइलर झंडे पूर्ण अनुकूलन के लिए सेट किए गए हैं (और विधानसभा अच्छी है, मैंने जाँच की)
( संपादित करें : स्टेफेंटायरोन सही ढंग से बताता है कि संख्याओं के लंबे तारों पर संचालन को वेक्टरिंग सिमडी पैक ऑप्स का उपयोग करना चाहिए, जैसे rsqrtps
- लेकिन यहां सरणी डेटा संरचना केवल परीक्षण के उद्देश्य के लिए है: जो मैं वास्तव में मापने की कोशिश कर रहा हूं वह कोड में उपयोग करने के लिए स्केलर प्रदर्शन है। इसे वेक्टर नहीं किया जा सकता है।)
inline float SSESqrt( float restrict fIn ) { float fOut; _mm_store_ss( &fOut, _mm_sqrt_ss( _mm_load_ss( &fIn ) ) ); return fOut; }
। लेकिन यह एक बुरा विचार है क्योंकि यह एक लोड-हिट-स्टोर स्टाल को आसानी से प्रेरित कर सकता है यदि सीपीयू फ़्लोट्स को स्टैक पर लिखता है और फिर उन्हें तुरंत वापस पढ़ता है - विशेष रूप से रिटर्न वैल्यू के लिए वेक्टर रजिस्टर से फ्लोट रजिस्टर में जुगाड़ करना। बुरी खबर है। इसके अलावा, अंतर्निहित मशीन से पता चलता है कि एसएसई इंट्रिनिक्स वैसे भी एड्रेस ऑपरेंड का प्रतिनिधित्व करता है।
eax
) के बीच चल रहा डेटा बहुत खराब है, जबकि xmm0 और स्टैक के बीच एक गोल यात्रा और इंटेल के स्टोर-फ़ॉरवर्डिंग के कारण वापस नहीं आया है। आप यह सुनिश्चित करने के लिए खुद देख सकते हैं। आमतौर पर संभावित एलएचएस को देखने का सबसे आसान तरीका उत्सर्जित विधानसभा को देखना है और यह देखना है कि डेटा रजिस्टर रजिस्टर के बीच कहां तक घूमा है; आपका कंपाइलर स्मार्ट काम कर सकता है, या यह नहीं हो सकता है। वैक्टर को सामान्य करने के लिए, मैंने अपने परिणाम यहाँ लिखे: bit.ly/9W5zoU