2012-05-04 42 views
20

इंटेल एडवांस्ड वेक्टर एक्सटेंशन (एवीएक्स) डबल परिशुद्धता फ्लोटिंग पॉइंट चर के लिए 256-बिट संस्करण (वाईएमएम रजिस्टर) में डॉट उत्पाद प्रदान करता है। "क्यों?" प्रश्न का एक और मंच (here) और स्टैक   ओवरफ्लो (here) पर बहुत संक्षिप्त रूप से इलाज किया गया है। लेकिन जिस प्रश्न का मैं सामना कर रहा हूं वह यह है कि इस लापता निर्देश को अन्य AVX निर्देशों के साथ एक कुशल तरीके से कैसे बदला जाए?इंटेल एवीएक्स: डबल परिशुद्धता फ्लोटिंग पॉइंट चर के लिए डॉट उत्पाद का 256-बिट संस्करण

256-बिट संस्करण में डॉट उत्पाद एकल परिशुद्धता चल बिन्दु चर के लिए मौजूद है (reference here):

__m256d _mm256_dp_pd(__m256d m1, __m256d m2, const int mask); 

:

__m256 _mm256_dp_ps(__m256 m1, __m256 m2, const int mask); 

विचार यह याद आ रही शिक्षा के लिए एक कुशल बराबर मिल रहा है अधिक विशिष्ट होने के लिए, कोड मैं __m128 (चार फ्लोट्स) से __m256d (4 युगल) से बदलना चाहता हूं, निम्नलिखित निर्देशों का उपयोग करें:

__m128 val0 = ...; // Four float values 
    __m128 val1 = ...; // 
    __m128 val2 = ...; // 
    __m128 val3 = ...; // 
    __m128 val4 = ...; // 

    __m128 res = _mm_or_ps(_mm_dp_ps(val1, val0, 0xF1), 
       _mm_or_ps(_mm_dp_ps(val2, val0, 0xF2), 
       _mm_or_ps(_mm_dp_ps(val3, val0, 0xF4), 
          _mm_dp_ps(val4, val0, 0xF8)))); 

इस कोड का परिणाम val1 और val0, val2 और val0, val3 और val0, val4 और val0 के बीच डॉट उत्पादों के परिणामों से युक्त चार तैरता के _m128 वेक्टर है।

शायद यह सुझावों के लिए संकेत दे सकता है?

+0

विचार के लिए धन्यवाद लेकिन मुझे अपने आवेदन में डबल परिशुद्धता रखना चाहिए। –

+0

इसके अलावा, रूपांतरण + फ्लोट डॉट उत्पाद को डबल डॉट उत्पाद की तुलना में अधिक समय लगेगा। – hirschhornsalz

उत्तर

21

मैं 4 * डबल गुणा का उपयोग करता हूं, फिर hadd (जो दुर्भाग्य से ऊपरी और निचले आधे में केवल 2 * 2 फ्लोट जोड़ता है), ऊपरी आधा निकालें (एक शफल को समान रूप से काम करना चाहिए, शायद तेज़) और इसे जोड़ें निचले आधे तक।

परिणाम निम्न 64 बिट dotproduct में है।

__m256d xy = _mm256_mul_pd(x, y); 
__m256d temp = _mm256_hadd_pd(xy, xy); 
__m128d hi128 = _mm256_extractf128_pd(temp, 1); 
__m128d dotproduct = _mm_add_pd((__m128d)temp, hi128); 

संपादित करें:
नॉर्बर्ट पी की एक विचार के बाद मैं इस संस्करण बढ़ाया एक समय में 4 डॉट उत्पादों क्या करना है।

__m256d xy0 = _mm256_mul_pd(x[0], y[0]); 
__m256d xy1 = _mm256_mul_pd(x[1], y[1]); 
__m256d xy2 = _mm256_mul_pd(x[2], y[2]); 
__m256d xy3 = _mm256_mul_pd(x[3], y[3]); 

// low to high: xy00+xy01 xy10+xy11 xy02+xy03 xy12+xy13 
__m256d temp01 = _mm256_hadd_pd(xy0, xy1); 

// low to high: xy20+xy21 xy30+xy31 xy22+xy23 xy32+xy33 
__m256d temp23 = _mm256_hadd_pd(xy2, xy3); 

// low to high: xy02+xy03 xy12+xy13 xy20+xy21 xy30+xy31 
__m256d swapped = _mm256_permute2f128_pd(temp01, temp23, 0x21); 

// low to high: xy00+xy01 xy10+xy11 xy22+xy23 xy32+xy33 
__m256d blended = _mm256_blend_pd(temp01, temp23, 0b1100); 

__m256d dotproduct = _mm256_add_pd(swapped, blended); 
+0

सुझाव के लिए धन्यवाद, यह अच्छी तरह से काम करता है। मैंने अधिक विशिष्ट होने के लिए अपना प्रश्न संपादित किया है। –

+6

डाउनवॉटर, समझाने की देखभाल? – hirschhornsalz

+0

धन्यवाद! क्या आप अंतिम पंक्ति समझा सकते हैं? मैं इसे अच्छी तरह से समझने के लिए शर्मिंदा नहीं हूँ। क्या यह '_mm256_add_pd' नहीं है? –

12

मैं drhirsch's answer का विस्तार ही समय में दो डॉट उत्पादों प्रदर्शन करने के लिए, कुछ काम की बचत होगी:

__m256d xy = _mm256_mul_pd(x, y); 
__m256d zw = _mm256_mul_pd(z, w); 
__m256d temp = _mm256_hadd_pd(xy, zw); 
__m128d hi128 = _mm256_extractf128_pd(temp, 1); 
__m128d dotproduct = _mm_add_pd((__m128d)temp, hi128); 

फिर dot(x,y) कम डबल में है और dot(z,w)dotproduct के उच्च डबल में है।

2

एक ही डॉट-उत्पाद के लिए, यह केवल एक लंबवत गुणा और क्षैतिज योग है (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 सी थ्रुपुट के साथ)।

 संबंधित मुद्दे

  • कोई संबंधित समस्या नहीं^_^