2011-11-14 25 views
20

अनुकूलित करना मैं गणित कोड लिख रहा हूं जिसे बड़ी संख्या में तेजी से गुणा करने की आवश्यकता है। यह एक पूर्णांक के साथ पूर्णांक की सरणी के गुणों को तोड़ देता है। C++ में यह इस तरह दिखता है (अहस्ताक्षरित के पर):x64 असेंबलर एमयूएल लूप

void muladd(unsigned* r, const unsigned* a, unsigned len, unsigned b) { 
    unsigned __int64 of = 0; // overflow 
    unsigned i = 0; // loop variable 
    while (i < len) { 
     of += (unsigned __int64)a[i] * b + r[i]; 
     r[i] = (unsigned)of; 
     of >>= 32; 
     ++i; 
    } 
    r[i] = (unsigned)of; // save overflow 
} 

मैं मैन्युअल रूप से इस पाश unrolled, यह 64 बिट में बदला जाएगा और .asm संकलक उत्पादन पर काम किया यह आगे अनुकूलन करने के लिए। मुख्य .asm पाश अब इस तरह दिखता है:

mov rax, rdi        ; rdi = b 
mul QWORD PTR [rbx+r10*8-64]    ; rdx:rax = a[i] * b; r10 = i 
mov rsi, QWORD PTR [r14+r10*8-64]  ; r14 = r; rsi = r[i] 
add rax, rsi 
adc rdx, 0 
add rax, r11        ; r11 = of (low part) 
adc rdx, 0 
mov QWORD PTR [r14+r10*8-64], rax  ; save result 
mov r11, rdx 

; this repeats itself 8 times with different offsets 

जब मैं बेंचमार्क इस, मुझे लगता है कि यह मेरे Core2 Quad पर गुणा प्रति avarage पर 6.3 के बारे में चक्र लेता है।

मेरा प्रश्न है: मैं इस में तेजी लाने के कर सकते हैं किसी भी तरह? दुर्भाग्यवश, मुझे जोड़ों में से किसी एक से बचने के लिए कोई रास्ता नहीं दिखता है और गुणा को हमेशा आरडीएक्स की आवश्यकता होती है: आरएक्स, इसलिए मुझे डेटा को चारों ओर स्थानांतरित करने की आवश्यकता है और "समानांतर में गुणा" की तरह नहीं हो सकता है।

कोई भी विचार किसी को भी?

अद्यतन: कुछ और परीक्षण के बाद, मैं प्रति 64-बिट एमयूएल 5.4 के बारे में चक्र की गति अप (है कि, सभी जोड़ने शामिल कदम और पाश भूमि के ऊपर) लाने के लिए प्रबंधित किया है। मुझे लगता है कि कोर 2 पर आपको सबसे अच्छा मिल सकता है, क्योंकि कोर 2 में बहुत तेज़ एमयूएल निर्देश नहीं है: इसमें 3 का थ्रूपुट और 6 (रेस 7) चक्र की विलम्ब है। सैंडी पुल 1 के थ्रूपुट और 3 (श्वास 4) चक्रों की विलम्ब के साथ बेहतर होगा।

जीएमपी के लिए बहुत कम संख्या के बारे में: मुझे मिल गया है कि उनके स्रोत कोड से और मुझे लगता है कि यह एक सैद्धांतिक संख्या है। लेकिन यह सुनिश्चित है कि यह एक संख्या है जिसे एएमडी के 9 सीपीयू के लिए गणना की गई थी। और जो मैंने पढ़ा है, उससे मैं एएमडी को (पुराने) इंटेल चिप्स की तुलना में तेज़ एमयूएल इकाई इकट्ठा करता हूं। अपनी दिनचर्या SSE से फायदा हो सकता है की तरह

+7

आप जीएमपी में विधानसभा दिनचर्या में से कुछ पर एक नज़र लेने के लिए चाहते हो सकता है। उनके पास ऐसा फ़ंक्शन है जो वास्तव में करता है और अधिकांश प्रोसेसर के लिए x64 सहित असेंबली में लिखा जाता है। – Mysticial

+0

जीएमपी वास्तव में एक तेजी से mul_basecase के लिए अच्छा समर्थन है और जाहिर है कि यह उन्हें प्रति एमयूएल के बारे में 2.35 चक्र लेता है, बहुत अच्छा है। अगर मैं इसे सही ढंग से समझता हूं, तो वे दो वैक्टरों को अंतःस्थापित करते हैं, जो निर्भरता को कम रखते हैं और अतिप्रवाह हैंडलिंग में सुधार करते हैं। – cxxl

उत्तर

0

लग रहा है। पीएमयूएलडीडी और पीएडीडीडी प्रासंगिक निर्देशों की तरह दिखता है। सुनिश्चित नहीं है कि आपका कंपाइलर एसएसई का उत्पादन क्यों नहीं करता है।

+0

यह 32-बिट x 32-बिट गुणों के लिए काम करता है। लेकिन 64-बिट x 64-बिट गुणा के लिए नहीं। – Mysticial

+1

क्या आपको वास्तव में सबसे महत्वपूर्ण कीवर्ड रखने पर qword गुणा की आवश्यकता है? –

+0

मैं आरएक्स को स्मृति में वापस सहेजता हूं और आरडीएक्स को ले जाने के रूप में उपयोग किया जाता है (आर 11 के माध्यम से) और अगले तत्व में जोड़ा जाता है। दुर्भाग्य से, मुझे QWORD MUL की आवश्यकता है। – cxxl

0

मैं यह इंगित करना चाहता हूं कि चक्र-गिनती बल्कि बेकार है क्योंकि आपके निर्देश माइक्रोक्रोड में परिवर्तित हो जाएंगे जिन्हें सीपीयू कर रहे सभी चीजों के आधार पर आदेश से बाहर कर दिया जाएगा या रोका जाएगा। यदि आपके पास तेजी से दिनचर्या है, जो आप करते हैं, तो सैद्धांतिक चक्र को आजमाने और दाढ़ी देने के लिए वास्तव में उपयोगी नहीं है जब तक कि आपको पता न हो कि आपका दिनचर्या हमेशा पूर्ण अलगाव में चलता रहेगा।

+1

ओपी ने अपना कोड बेंचमार्क किया, और स्पष्ट रूप से दोहराने योग्य परिणाम प्राप्त हुए। उन्होंने सैद्धांतिक चक्रों की गणना नहीं की, उन्होंने वास्तव में व्यावहारिक चक्रों को माप लिया। निर्देशों का अनुवाद माइक्रोकोड में किया जाता है और फिर से व्यवस्थित किया जाता है अनुमानित और बहुत अच्छी तरह से जाना जाता है (www.agner.org देखें)। इसके अलावा _complete_ अलगाव को अनुकूलित करने के लिए जरूरी नहीं है, कोड चलाने वाली पृष्ठभूमि में एक ओएस आमतौर पर कुछ प्रतिशत से अधिक दाढ़ी नहीं देगा, अगर बिल्कुल भी। – hirschhornsalz

+0

ओह क्षमा करें, मुझे याद आया कि उसने इसे प्रोफाइल किया :) – Tobias

+0

यह एक टिप्पणी होना चाहिए, जवाब नहीं। –

1

मैंने एक बार एक लूप लिखा जो इस तरह दिखता है, जिसके परिणामस्वरूप लूप को स्मृति गति से सीमित किया गया था, जिसके परिणामस्वरूप बहुत सारे डेटा पर प्रसंस्करण की न्यूनतम मात्रा थी।

जब यह परिणाम काम करता है मैं प्रीफ़ेचिंग एक [i] और आर [i]

अगर जीसीसी कोडांतरक

http://gcc.gnu.org/onlinedocs/gcc-3.3.6/gcc/Other-Builtins.html

में समारोह __builtin_prefetch() या PREFETCHT0 अनुदेश का उपयोग का उपयोग कर प्रयास करता हूँ नाटकीय हो सकता है। जब तक लूप एक हजार या उससे अधिक लंबे समय तक चलता है, तो मैं एक प्रारंभिक बिंदु के रूप में एक [i + 64] और r [i + 64] prefetch करता हूं और देखता हूं कि यह आपके CPU पर कितना अंतर बनाता है। आपको बड़ी प्रीफेच दूरी की कोशिश करनी पड़ सकती है।

+1

मैंने कोशिश की। नतीजा यह था कि यह मेरे कोर 2 क्वाड पर कोई फर्क नहीं पड़ता। सीपीयू मैनुअल और एग्नेर फोग के गाइड के माध्यम से स्किमिंग, मुझे यह विचार मिलता है कि आज के प्रोसेसर के पास एक अच्छा प्रीफेच तर्क है जो सरल लूप को बहुत अच्छी तरह से पहचानता है, इसलिए मैन्युअल प्रीफेचिंग आवश्यक नहीं है। – cxxl

0

क्या आर कॉल से पहले कुछ भी महत्वपूर्ण है?

यदि ऐसा होता है, और आप इसे जमा कर रहे हैं, तो अब पढ़ना बंद करें।

यदि ऐसा नहीं होता है (यानी आप हमेशा शून्य पर जमा हो रहे हैं), और यह मानते हुए कि आप इस फ़ंक्शन को कैश आकारों से काफी बड़े एरे पर आक्रमण कर रहे हैं, तो मैं आवश्यकता को खत्म करने के लिए एक तरीका ढूंढ रहा हूं आर से पढ़ें और "सहेजें परिणाम" MOV को MOVNT (_mm_stream_ps इंट्रिनिक्स में) रूपांतरित करने के लिए परिवर्तित करें।

यह महत्वपूर्ण प्रदर्शन में सुधार कर सकते हैं। कैसे ? वर्तमान में आपके कैश एक से कैश-लाइन ला रहे हैं, आर से कैश लाइनें ला रहे हैं और कैश लाइनों को वापस लिख रहे हैं। तथाकथित स्ट्रीमिंग स्टोर्स के साथ आप केवल कैश लाइनों को एक से लिखते हैं और सीधे लिखते हैं: बहुत कम बस यातायात। यदि आप किसी भी आधुनिक सीआरटी के memcpy कार्यान्वयन को देखते हैं तो यह कुछ कैश-आकार से संबंधित थ्रेसहोल्ड के ऊपर स्ट्रीमिंग स्टोर्स का उपयोग करने के लिए स्विच करेगा (और almost twice as fast को पारंपरिक चालों का उपयोग करके एक memcpy के रूप में चलाएं)।

+0

यह बहुत दिलचस्प है। समारोह की कॉल पर 'आर' खाली है, लेकिन धीरे-धीरे भर जाता है। इसके अलावा, समारोह पूरा होने के बाद, मुझे उम्मीद है कि इसका उपयोग कुछ के लिए किया जाएगा (क्योंकि यह परिणाम है :))। मुझे उम्मीद है कि MOVNT लाभकारी नहीं होगा, क्योंकि हम अनुक्रमिक तरीके से 'आर' भर रहे हैं। एग्नेर फोग लिखते हैं, "कैशिंग के बिना डेटा स्टोर करने की विधि फायदेमंद है, और केवल तभी, एक स्तर -2 कैश मिस की उम्मीद की जा सकती है" (http://www.agner.org/optimize/optimizing_cpp.pdf)। मुझे लगता है कि 99% में एल 2 कैश मिस से इंकार किया जा सकता है। – cxxl