2012-03-22 13 views
11

मैं वर्तमान में एक सी ++ टेम्पलेट अभिव्यक्ति लाइब्रेरी लिख रहा हूं और असेंबली स्तर पर हाथ से लिखित कोड के साथ कुछ तत्कालताओं की तुलना कर रहा हूं।अभिव्यक्ति टेम्पलेट बनाम हाथ से लिखित कोड

spinor multiply(vector const& a, vector const& b) 
{ 
     spinor result = { 
       a.at<1>() * b.at<1>() - a.at<2>() * b.at<2>() 
          - a.at<4>() * b.at<4>() - a.at<8>() * b.at<8>(), 
       a.at<1>() * b.at<2>() - a.at<2>() * b.at<1>(), 
       a.at<1>() * b.at<4>() - a.at<4>() * b.at<1>(), 
       a.at<1>() * b.at<8>() - a.at<8>() * b.at<1>(), 
       a.at<2>() * b.at<4>() - a.at<4>() * b.at<2>(), 
       a.at<2>() * b.at<8>() - a.at<8>() * b.at<2>(), 
       a.at<4>() * b.at<8>() - a.at<8>() * b.at<4>() 
     }; 

     return result; 
} 

vector वर्ग सिर्फ चार युगल पर एक आवरण जो at<index>() सदस्य फ़ंक्शन का उपयोग करके पढ़ा जा सकता है है: हाथ से लिखा समारोह निम्नलिखित है। डिजाइन निर्णयों के कारण, चार घटकों के सूचकांक 1, 2, 4, 8 हैं जिन्हें सामान्य 0, 1, 2, 3 के बजाय at<index>() के साथ उपयोग किया जाता है।

इस फ़ंक्शन का उद्देश्य दो वैक्टरों (मिंकोस्की-स्पेस में) के गुणा के परिणाम को वापस करना है। यदि आप जियोमेट्रिक बीजगणित से परिचित हैं, तो आपको result का पहला घटक, a और b के आदान-प्रदान के तहत सममित है) और वेज-उत्पाद (शेष घटकों, एंटीसिमेट्रिक a और b के आदान-प्रदान के तहत)। यदि आप जियोमेट्रिक बीजगणित से परिचित नहीं हैं, तो इस कार्य को वैक्टरों को गुणा करने के लिए एक नुस्खे के रूप में लें।

अगर मैं जीसीसी 4.7 के साथ ऊपर समारोह संकलन और objdump -SC a.out इस द्वारा दिए गए disassembly को देखो मेरा पीछा उत्पादन देता है:

400bc0: movsd 0x8(%rsi),%xmm6 
400bc5: mov %rdi,%rax 
400bc8: movsd (%rsi),%xmm8 
400bcd: movsd 0x8(%rdx),%xmm5 
400bd2: movapd %xmm6,%xmm9 
400bd7: movsd (%rdx),%xmm7 
400bdb: movapd %xmm8,%xmm0 
400be0: mulsd %xmm5,%xmm9 
400be5: movsd 0x10(%rsi),%xmm4 
400bea: mulsd %xmm7,%xmm0 
400bee: movsd 0x10(%rdx),%xmm1 
400bf3: movsd 0x18(%rdx),%xmm3 
400bf8: movsd 0x18(%rsi),%xmm2 
400bfd: subsd %xmm9,%xmm0 
400c02: movapd %xmm4,%xmm9 
400c07: mulsd %xmm1,%xmm9 
400c0c: subsd %xmm9,%xmm0 
400c11: movapd %xmm3,%xmm9 
400c16: mulsd %xmm2,%xmm9 
400c1b: subsd %xmm9,%xmm0 
400c20: movapd %xmm6,%xmm9 
400c25: mulsd %xmm7,%xmm9 
400c2a: movsd %xmm0,(%rdi) 
400c2e: movapd %xmm5,%xmm0 
400c32: mulsd %xmm8,%xmm0 
400c37: subsd %xmm9,%xmm0 
400c3c: movapd %xmm4,%xmm9 
400c41: mulsd %xmm7,%xmm9 
400c46: mulsd %xmm2,%xmm7 
400c4a: movsd %xmm0,0x8(%rdi) 
400c4f: movapd %xmm1,%xmm0 
400c53: mulsd %xmm8,%xmm0 
400c58: mulsd %xmm3,%xmm8 
400c5d: subsd %xmm9,%xmm0 
400c62: subsd %xmm7,%xmm8 
400c67: movapd %xmm4,%xmm7 
400c6b: mulsd %xmm5,%xmm7 
400c6f: movsd %xmm0,0x10(%rdi) 
400c74: mulsd %xmm2,%xmm5 
400c78: movapd %xmm1,%xmm0 
400c7c: mulsd %xmm6,%xmm0 
400c80: movsd %xmm8,0x18(%rdi) 
400c86: mulsd %xmm3,%xmm6 
400c8a: mulsd %xmm2,%xmm1 
400c8e: mulsd %xmm4,%xmm3 
400c92: subsd %xmm7,%xmm0 
400c96: subsd %xmm5,%xmm6 
400c9a: subsd %xmm1,%xmm3 
400c9e: movsd %xmm0,0x20(%rdi) 
400ca3: movsd %xmm6,0x28(%rdi) 
400ca8: movsd %xmm3,0x30(%rdi) 
400cad: retq 
400cae: nop 
400caf: nop 

यह मेरे लिए बहुत अच्छा लग रहा है - पहले के घटकों (%rsi) और दूसरा (%rdx) वैक्टर केवल एक बार उपयोग किए जाते हैं और वास्तविक गणना केवल रजिस्टरों में ही की जाती है। अंत में, परिणाम %rdi रजिस्टर में पते पर लिखा गया है। चूंकि यह पहला तर्क रजिस्टर है, मुझे लगता है कि रिटर्न वैल्यू ऑप्टिमाइज़ेशन यहां नियोजित है।

ऊपर समारोह की अभिव्यक्ति टेम्पलेट संस्करण के लिए सूची निम्नलिखित के साथ इस की तुलना करें:

400cb0: mov (%rsi),%rdx 
400cb3: mov 0x8(%rsi),%rax 
400cb7: movsd 0x1f1(%rip),%xmm4  # 400eb0 <_IO_stdin_used+0x10> 
400cbe: 
400cbf: movsd 0x10(%rdx),%xmm3 
400cc4: movsd 0x18(%rdx),%xmm0 
400cc9: mulsd 0x10(%rax),%xmm3 
400cce: xorpd %xmm4,%xmm0 
400cd2: mulsd 0x18(%rax),%xmm0 
400cd7: movsd 0x8(%rdx),%xmm2 
400cdc: movsd (%rdx),%xmm1 
400ce0: mulsd 0x8(%rax),%xmm2 
400ce5: mulsd (%rax),%xmm1 
400ce9: subsd %xmm3,%xmm0 
400ced: subsd %xmm2,%xmm0 
400cf1: addsd %xmm0,%xmm1 
400cf5: movsd %xmm1,(%rdi) 
400cf9: movsd (%rdx),%xmm0 
400cfd: movsd 0x8(%rdx),%xmm1 
400d02: mulsd 0x8(%rax),%xmm0 
400d07: mulsd (%rax),%xmm1 
400d0b: subsd %xmm1,%xmm0 
400d0f: movsd %xmm0,0x8(%rdi) 
400d14: movsd (%rdx),%xmm0 
400d18: movsd 0x10(%rdx),%xmm1 
400d1d: mulsd 0x10(%rax),%xmm0 
400d22: mulsd (%rax),%xmm1 
400d26: subsd %xmm1,%xmm0 
400d2a: movsd %xmm0,0x10(%rdi) 
400d2f: movsd 0x8(%rdx),%xmm0 
400d34: movsd 0x10(%rdx),%xmm1 
400d39: mulsd 0x10(%rax),%xmm0 
400d3e: mulsd 0x8(%rax),%xmm1 
400d43: subsd %xmm1,%xmm0 
400d47: movsd %xmm0,0x18(%rdi) 
400d4c: movsd (%rdx),%xmm0 
400d50: movsd 0x18(%rdx),%xmm1 
400d55: mulsd 0x18(%rax),%xmm0 
400d5a: mulsd (%rax),%xmm1 
400d5e: subsd %xmm1,%xmm0 
400d62: movsd %xmm0,0x20(%rdi) 
400d67: movsd 0x8(%rdx),%xmm0 
400d6c: movsd 0x18(%rdx),%xmm1 
400d71: mulsd 0x18(%rax),%xmm0 
400d76: mulsd 0x8(%rax),%xmm1 
400d7b: subsd %xmm1,%xmm0 
400d7f: movsd %xmm0,0x28(%rdi) 
400d84: movsd 0x10(%rdx),%xmm0 
400d89: movsd 0x18(%rdx),%xmm1 
400d8e: mulsd 0x18(%rax),%xmm0 
400d93: mulsd 0x10(%rax),%xmm1 
400d98: subsd %xmm1,%xmm0 
400d9c: movsd %xmm0,0x30(%rdi) 
400da1: retq 

इस समारोह के हस्ताक्षर

spinor<product<vector, vector>>(product<vector, vector> const&) 

है मुझे आशा है कि आप मुझ पर भरोसा है कि दोनों संस्करण एक ही देना परिणाम। पहली दो पंक्तियां पहले और दूसरे वेक्टर निकालने वाली हैं जिन्हें product में संदर्भ के रूप में संग्रहीत किया जाता है। मैंने निम्नलिखित बातों के बारे में सोचा:

  • xorpd %xmm4,%xmm0 के साथ संयोजन में movsd 0x1f1(%rip),%xmm4 क्या करता है? मुझे पहले ही पता चला है कि इसे "आरआईपी सापेक्ष एड्रेसिंग" कहा जाता है, http://www.x86-64.org/documentation/assembly.html
  • जीसीसी अधिक रजिस्टरों का उपयोग क्यों नहीं करता है, उदाहरण के लिए 0x10(%rax) कैश करने के लिए जो चार बार पढ़ा जाता है?

मैं भी 100000000 यादृच्छिक वैक्टर पैदा करने और समय दोनों कार्यों के लिए आवश्यक लेने के द्वारा दोनों कार्यों बेंचमार्क:

ET: 7.5 sec 
HW: 6.8 sec 

हाथ से लिखा समारोह के बारे में 10% तेजी से होता है। क्या किसी को अभिव्यक्ति टेम्पलेट्स का अनुभव है और जानता है कि उन्हें अपने हाथ से लिखे गए समकक्ष के करीब कैसे करना है?

+0

आपकी समस्या से कोई लेना देना नहीं, लेकिन यहां तक ​​कि स्कॉइंग नियमों के साथ भी मैं कुछ मानक कंटेनर कक्षाओं के समान नए वर्गों को नामित करने की कोशिश नहीं करता हूं। यदि आप 'नेमस्पेस std का उपयोग कर' का उपयोग करते हैं, तो चीजें बहुत भ्रमित हो सकती हैं। –

+2

@ यह वास्तविक ईटी कोड के बिना कठिन होने जा रहा है। साझा करने के लिए परवाह? – sehe

+1

@ जोचिमपिलबोर्ग: यदि आप 'नेमस्पेस std का उपयोग करते हुए' का उपयोग करते हैं, तो मुझे कोई परवाह नहीं है कि आपको समस्याएं मिलती हैं (आपको अधिक विशिष्ट होना चाहिए) ... लेकिन मैं नाम के संघर्ष से बचने की कोशिश करने के बारे में सामान्य विचार से सहमत हूं। –

उत्तर

3

अगर हम यकीन है कि पता 0x400eb0 की सामग्री के लिए पता था कि यह स्पष्ट होगा, लेकिन मुझे लगता है यह 0x8000 0000 0000 0000 8000 0000 0000 0000, या इसी तरह की है (संभवतः एक प्रमुख 0 के साथ, क्योंकि कोड vectorized नहीं है), 128 बिट पूर्णांक के रूप में लिखा।

उस स्थिति में xorpd दूसरे ऑपरेंड के संकेत को बदलता है।

रजिस्टर पढ़ने के लिए कैश नहीं किया गया है - जीसीसी-सहायता मेलिंग सूची पर बेहतर पूछें। संभवतः संकलक यह साबित नहीं कर सकता कि दो वैक्टर या मध्यवर्ती परिणाम उपनाम नहीं हैं।

लेकिन सामान्य राय के मुकाबले, कंपाइलर्स हमेशा पूरी तरह अनुकूल नहीं होते हैं, लेकिन सभी प्रोग्रामर के 90% (या 99%?) से बेहतर होते हैं (यदि वे असेंबली लिखने का प्रयास करते हैं), और कभी-कभी (शायद ही कभी) वे वास्तव में धीमे होते हैं कोड।

लेकिन आपका दृष्टिकोण बहुत अच्छा है - बेंचमार्किंग और उत्पन्न ऑब्जेक्ट कोड देखना सही है यदि आप अनुकूलित करना चाहते हैं।

पीएस: वे कोड mulsd के बजाय वेक्टर निर्देशों (mulpd) का उपयोग करके संभवतः त्वरित हो सकते हैं, जो एक बार में दो या चार युगल गुणा करता है), उर्फ ​​एसएसई या एवीएक्स। लेकिन रजिस्टरों में सही जगहों पर मूल्यों को घुमाने के लिए कुछ निर्देशों की आवश्यकता है, इसलिए लाभ हमेशा दो या चार गुना से धीमा होता है।

+0

मुझे इस पते पर भी पता नहीं है, यह मेरे कोड के अंत से परे इंगित कर रहा है (objdump द्वारा उत्पन्न टिप्पणी देखें)। यहां एक पूर्ण प्रिंटआउट है: – cschwan

+0

http://www.students.uni-mainz.de/cschwan/asm.out (पुराना संस्करण, लेकिन स्थिति बनी हुई है, लाइन 373 देखें) – cschwan

+0

मैंने एसएसई का उपयोग करने के बारे में भी सोचा, लेकिन मुझे लगता है एसएसई इंट्रिनिक्स के साथ अभिव्यक्ति टेम्पलेट्स को गठबंधन करना बहुत मुश्किल है, विशेष रूप से क्योंकि गणना में संकेत हो सकते हैं और संभवतः बहुत अलग हैं ('गुणा' में पहले और दूसरे घटक की तुलना करें)। – cschwan

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