एक ही डॉट-उत्पाद के लिए, यह केवल एक लंबवत गुणा और क्षैतिज योग है (Fastest way to do horizontal float vector sum on x86 देखें)। hadd
लागत 2 शफल + add
। जब इनपुट दोनों = एक ही वेक्टर के साथ प्रयोग किया जाता है तो यह लगभग हमेशा उप-इष्टतम होता है।
// both elements = dot(x,y)
__m128d dot1(__m256d x, __m256d y) {
__m256d xy = _mm256_mul_pd(x, y);
__m128d xylow = _mm256_castps256_pd128(xy); // (__m128d)cast isn't portable
__m128d xyhigh = _mm256_extractf128_pd(xy, 1);
__m128d sum1 = _mm_add_pd(xylow, xyhigh);
__m128d swapped = _mm_shuffle_pd(sum1, sum1, 0b01); // or unpackhi
__m128d dotproduct = _mm_add_pd(sum1, swapped);
return dotproduct;
}
आप केवल एक डॉट उत्पाद की जरूरत है, इस इंटेल पर 1 फेरबदल UOP, और एएमडी जगुआर/बुलडोजर-परिवार/Ryzen पर एक बड़ा जीत से @ hirschhornsalz के एकल वेक्टर जवाब की तुलना में बेहतर है, क्योंकि यह करने के लिए और भी अधिक संकीर्णता 256 बी सामान का एक गुच्छा करने के बजाय 128b तुरंत। एएमडी 256 बी ऑप्स को दो 128 बी यूपीएस में विभाजित करता है।
यह समानांतर में 2 या 4 डॉट उत्पादों जहां 2 अलग इनपुट वैक्टर के साथ प्रयोग कर रहे हैं कर रही है जैसे मामलों में hadd
का उपयोग कर के लायक हो सकता है। यदि आप परिणाम पैक करना चाहते हैं तो वैक्टर के दो जोड़े के नॉरबर्ट के dot
इष्टतम दिखते हैं। मुझे लेन-क्रॉसिंग शफल के रूप में AVX2 vpermpd
के साथ भी बेहतर करने का कोई तरीका नहीं दिख रहा है।
बेशक यदि आप वास्तव में एक बड़ा dot
(8 या उससे अधिक double
एस के) चाहते हैं, ऊर्ध्वाधर का उपयोग add
(कई एक्युमुलेटरों vaddps
विलंबता को छिपाने के लिए के साथ) और अंत में क्षैतिज जोड़ने पर है। यदि आप उपलब्ध हैं तो fma
का भी उपयोग कर सकते हैं।
haddpd
आंतरिक xy
और zw
एक साथ दो अलग अलग तरीकों shuffles और खिलाती है कि एक ऊर्ध्वाधर addpd
के लिए, और है कि हम हाथ से वैसे भी करना चाहते हैं क्या है। अगर हमने xy
और zw
अलग रखा है, तो हमें एक डॉट उत्पाद (अलग रजिस्टरों में) प्राप्त करने के लिए प्रत्येक के लिए 2 शफल + 2 जोड़ना होगा। तो उन्हें पहले चरण के रूप में hadd
के साथ एक साथ जोड़कर, हम केवल जोड़ों और कुल यूओपी गिनती पर शफल की कुल संख्या को सहेजते हैं।
/* Norbert's version, for an Intel CPU:
__m256d temp = _mm256_hadd_pd(xy, zw); // 2 shuffle + 1 add
__m128d hi128 = _mm256_extractf128_pd(temp, 1); // 1 shuffle (lane crossing, higher latency)
__m128d dotproduct = _mm_add_pd((__m128d)temp, hi128); // 1 add
// 3 shuffle + 2 add
*/
लेकिन एएमडी, जहां vextractf128
बहुत सस्ता है के लिए, और 256b hadd
जितना 128b hadd
2x लागत, यह समझ बनाने के लिए अलग से 128b करने के लिए नीचे प्रत्येक 256b उत्पाद संकीर्ण करने के लिए कर सकता है और फिर एक 128b hadd के साथ गठबंधन।
दरअसल, Agner Fog's tables के अनुसार, haddpd xmm,xmm
Ryzen पर 4 यूओएस है। (और 256 बी याम संस्करण 8 यूओएस है)। इसलिए यह डेटा सही है, तो वास्तव में 2x vshufpd
+ vaddpd
का उपयोग मैन्युअल रूप से बेहतर है। यह नहीं हो सकता है: पिलिड्रिवर के लिए उनके डेटा में 3 यूओपी haddpd xmm,xmm
है, और यह मेमोरी ऑपरेंड के साथ केवल 4 यूओपीएस है। यह मुझे समझ में नहीं आता है कि वे hadd
को केवल 3 (या ymm के लिए 6) के रूप में लागू नहीं कर सके।
एक __m256d
में पैक परिणामों के साथ 4 dot
रों कर के लिए, सटीक पूछा समस्या, मुझे लगता है @ hirschhornsalz के जवाब इंटेल CPU के लिए बहुत अच्छा लग रहा है। मैंने इसे बहुत सावधानीपूर्वक अध्ययन नहीं किया है, लेकिन hadd
के साथ जोड़े में संयोजन अच्छा है। vperm2f128
इंटेल पर कुशल है (लेकिन एएमडी पर काफी बुरा: रेजन पर 8 यूपीएस प्रति 3 सी थ्रुपुट के साथ)।
विचार के लिए धन्यवाद लेकिन मुझे अपने आवेदन में डबल परिशुद्धता रखना चाहिए। –
इसके अलावा, रूपांतरण + फ्लोट डॉट उत्पाद को डबल डॉट उत्पाद की तुलना में अधिक समय लगेगा। – hirschhornsalz