gcc -O3
तो यह पाश-जाता निर्भरता श्रृंखला लंबा सशर्त के लिए एक cmov का उपयोग करता है, को शामिल करने के लिए एक cmov
(2 UOPs और अपने इंटेल Sandybridge CPU पर विलंबता के 2 चक्र है जो Agner Fog's instruction tables के अनुसार, x86 टैग विकी भी देखें)। यह one of the cases where cmov
sucks है।
यदि डेटा भी मामूली अप्रत्याशित था, तो cmov
शायद एक जीत होगी, इसलिए यह एक कंपाइलर बनाने के लिए एक काफी समझदार विकल्प है। (हालांकि, compilers may sometimes use branchless code too much।)
आई put your code on the Godbolt compiler explorer एएसएम देखने के लिए (अच्छी हाइलाइटिंग और अप्रासंगिक लाइनों को फ़िल्टर करने के साथ। आपको अभी भी मुख्य(), हालांकि प्राप्त करने के लिए सभी प्रकार के कोड को स्क्रॉल करना होगा।
.L82: # the inner loop from gcc -O3
movsx rcx, DWORD PTR [rdx] # sign-extending load of data[c]
mov rsi, rcx
add rcx, rbx # rcx = sum+data[c]
cmp esi, 127
cmovg rbx, rcx # sum = data[c]>127 ? rcx : sum
add rdx, 4 # pointer-increment
cmp r12, rdx
jne .L82
जीसीसी एडीडी के बजाय एलआईए का उपयोग करके एमओवी को बचा सकता था।
एडीडी-> सीएमओवी (3 चक्र) की विलंबता पर लूप बाधाएं, चूंकि लूप का एक पुनरावृत्ति सीएमओ के साथ आरबीएक्स लिखता है, और अगला पुनरावृत्ति एडीडी के साथ आरबीएक्स पढ़ता है।
लूप में केवल 8 फ़्यूज्ड-डोमेन यूओएस हैं, इसलिए यह प्रति 2 चक्रों में जारी हो सकता है। निष्पादन-पोर्ट दबाव भी sum
डेप चेन की विलंबता के रूप में एक बाधा जितना खराब नहीं है, लेकिन यह करीब है (सैंडब्रिज में केवल 3 एएलयू बंदरगाह हैं, हैसल के 4 के विपरीत)।
बीटीडब्लू, इसे sum += (data[c] >= 128 ? data[c] : 0);
के रूप में लिखने के लिए cmov
लूप-ले जाने वाली डेप श्रृंखला से बाहर ले जाने के लिए संभावित रूप से उपयोगी है। अभी भी बहुत सारे निर्देश हैं, लेकिन प्रत्येक पुनरावृत्ति में cmov
स्वतंत्र है। यह compiles as expected in gcc6.3 -O2
and earlier, लेकिन गंभीर पथ (https://gcc.gnu.org/bugzilla/show_bug.cgi?id=82666) पर gcc7 de-cmov
में ऑप्ट-इन करता है। (यह भी यह लिखने की if()
तरीका से पहले जीसीसी संस्करणों के साथ ऑटो vectorizes।)
बजना भी मूल स्रोत के साथ महत्वपूर्ण मार्ग बंद cmov लेता है।
gcc -O2
है, जो अच्छी तरह से भविष्यवाणी की है क्योंकि अपने डेटा सॉर्ट हो जाता है (gcc5.x और बड़ी उम्र के लिए) एक शाखा उपयोग करता है। चूंकि आधुनिक सीपीयू नियंत्रण निर्भरताओं को संभालने के लिए शाखा-भविष्यवाणी का उपयोग करते हैं, इसलिए लूप-संचालित निर्भरता श्रृंखला कम है: केवल add
(1 चक्र विलंबता)।
प्रत्येक पुनरावृत्ति में तुलना-और-शाखा स्वतंत्र है, शाखा-भविष्यवाणी + सट्टा निष्पादन के लिए धन्यवाद, जो सुनिश्चित करने के लिए शाखा दिशा से पहले निष्पादन जारी रखने देता है। sum
और पाश-काउंटर:
.L83: # The inner loop from gcc -O2
movsx rcx, DWORD PTR [rdx] # load with sign-extension from int32 to int64
cmp ecx, 127
jle .L82 # conditional-jump over the next instruction
add rbp, rcx # sum+=data[c]
.L82:
add rdx, 4
cmp rbx, rdx
jne .L83
दो लूप-जाता निर्भरता चेन रहे हैं। sum
0 या 1 चक्र लंबा है, और लूप-काउंटर हमेशा 1 चक्र लंबा होता है। हालांकि, लूप सैंडीब्रिज पर 5 फ़्यूज्ड-डोमेन यूप्स है, इसलिए यह 1 सी प्रति पुनरावृत्ति पर निष्पादित नहीं हो सकता है, इसलिए विलंबता एक बाधा नहीं है।
यह शायद 2 चक्रों (शाखा निर्देश थ्रूपुट पर बाधा) के बारे में एक पुनरावृत्ति पर चलता है, बनाम -ओ 3 लूप के लिए प्रति 3 चक्र बनाम। अगली बाधा एएलयू यूओपी थ्रूपुट होगी: 4 एएलयू यूओप्स (नहीं लिया गया मामला) लेकिन केवल 3 एएलयू बंदरगाहों। (एडीडी किसी भी बंदरगाह पर चला सकते हैं)।
यह पाइपलाइन-विश्लेषण पूर्वानुमान ~ 3 सेकंड के लिए ~ 3 सेकंड के लिए -ओ 3 बनाम ~ 2 सेकंड के लिए बिल्कुल सटीक रूप से मेल खाता है।
Haswell/Skylake, एक 1.25 प्रति चक्र पर नहीं-ले जाया मामले चलाने के बाद से यह एक ले लिया शाखा के रूप में एक ही चक्र में एक नहीं लिया शाखा निष्पादित कर सकते हैं और 4 ALU बंदरगाह हैं सकता है। (या a 5 uop loop doesn't quite issue at 4 uops every cycle के बाद से थोड़ा कम)।
(बस का परीक्षण किया:।। Skylake @ 3.9GHz 1.45s में पूरे कार्यक्रम की टहनीदार संस्करण, या 1.68s में शाखा संस्करण चलाता है तो अंतर बहुत छोटा है वहाँ)
g + +6.3.1 cmov
का उपयोग -O2
पर भी करता है, लेकिन जी ++ 5.4 अभी भी 4.9.2 की तरह व्यवहार करता है।
दोनों जी ++ 6.3.1 और जी ++ 5.4 के साथ
, -fprofile-generate
/-fprofile-use
का उपयोग कर भी -O3
(-fno-tree-vectorize
के साथ) पर टहनीदार संस्करण पैदा करता है।
नए जीसीसी से लूप का सीएमओवी संस्करण सीएमपी/सीएमओवी के बजाय add ecx,-128
/cmovge rbx,rdx
का उपयोग करता है। यह थोडा अजीब है, लेकिन शायद इसे धीमा नहीं करता है। एडीडी एक आउटपुट रजिस्टर के साथ-साथ झंडे लिखता है, इसलिए भौतिक रजिस्टरों की संख्या पर अधिक दबाव पैदा करता है। लेकिन जब तक यह एक बाधा नहीं है, यह लगभग बराबर होना चाहिए।
नए जीसीसी ऑटो vectorizes -O3 साथ पाश है, जो यहां तक कि बस SSE2 के साथ एक महत्वपूर्ण speedup है। (उदाहरण के लिए मेरा i7-6700k स्काइलेक वेक्टरीकृत संस्करण 0.74s में चलाता है, इसलिए स्केलर के रूप में लगभग दोगुना तेज़ या -O3 -march=native
0.35s में, AVX2 256b वैक्टर का उपयोग करके)।
वेक्टरिज्ड संस्करण बहुत सारे निर्देशों की तरह दिखता है, लेकिन यह बहुत बुरा नहीं है, और उनमें से अधिकतर लूप-ले जाने वाले डेप चेन का हिस्सा नहीं हैं। इसे केवल अंत में 64-बिट तत्वों को अनपैक करना होगा। यह pcmpgtd
दो बार करता है, हालांकि, यह महसूस नहीं करता है कि यह साइन-विस्तार के बजाय शून्य-विस्तारित हो सकता है जब स्थिति पहले से ही सभी नकारात्मक पूर्णांक को शून्य कर देती है।
क्या आपने अपने कार्यक्रम को कई बार बेंचमार्क किया था? आपका सटीक प्रोसेसर क्या है? आपके पास क्या सटीक कोड है? क्या आपने 'gcc -O3 -mtune = native' के साथ संकलित करने का प्रयास किया था? और * कई बार * एक प्रोग्राम चलाने के लिए सुनिश्चित करें जो कुछ सेकंड तक रहता है (सेंटीसेकंड नहीं)। –
क्या आपने एक बार प्रत्येक कार्यक्रम चलाया था? आपको कुछ बार कोशिश करनी चाहिए। यह भी सुनिश्चित करें कि आपके द्वारा बेंचमार्किंग के लिए उपयोग की जाने वाली मशीन पर कुछ और नहीं चल रहा है, – doctorlove
@ बेसिलस्टारनकेविच मैं कोड जोड़ता हूं।मैं कई बार कोशिश करता हूं और एक ही परिणाम देता हूं। मैं '-mtune = native' के साथ संकलित करने का प्रयास करता हूं - जैसा कि पहले (इस ध्वज के बिना) के समान परिणाम। प्रोसेसर - इंटेल कोर i5 -2400 –