क्या नीचे दिए गए फ़ंक्शन पूरी तरह से में लूप को अनलोल करने के लिए जीसीसी (संस्करण I का उपयोग 4.8.4) को निर्देशित करने का कोई तरीका है, यानी, इस लूप को छीलें? लूप के पुनरावृत्तियों की संख्या संकलन समय पर ज्ञात है: 58.इस लूप को पूरी तरह से अनलॉक करने के लिए जीसीसी से कैसे पूछें (यानी, इस लूप को छीलें)?
मुझे सबसे पहले बताएं कि मैंने क्या प्रयास किया है।
गैस ouput की जाँच करके:
gcc -fpic -O2 -S GEPDOT.c
12 रजिस्टरों XMM0 - XMM11 किया जाता है। अगर मैं झंडा -funroll-छोरों जीसीसी के पास:
gcc -fpic -O2 -funroll-loops -S GEPDOT.c
पाश केवल दो बार unrolled है। मैंने जीसीसी अनुकूलन विकल्पों की जांच की। जीसीसी का कहना है कि -Funroll-loops-frename-registers चालू हो जाएगा, इसलिए जब जीसीसी एक लूप को अनलॉक करता है, तो पंजीकरण आवंटन के लिए इसकी पूर्व पसंद रजिस्टरों का "बाएं ओवर" का उपयोग करना है। लेकिन XMM12 - XMM15 पर केवल 4 शेष हैं, इसलिए जीसीसी केवल सर्वोत्तम रूप से 2 बार अनलॉक कर सकता है। 16 एक्सएमएम रजिस्टरों के बदले 48 रहे थे, जीसीसी बिना किसी परेशानी के 4 गुना लूप को अनलॉक कर देगा।
फिर भी मैंने एक और प्रयोग किया। मैंने पहली बार लूप को दो बार मैन्युअल रूप से अनलॉक किया, एक समारोह GEPDOT_2 प्राप्त किया। तो फिर वहाँ है कोई फर्क नहीं बिल्कुल के बीच
gcc -fpic -O2 -S GEPDOT_2.c
और
gcc -fpic -O2 -funroll-loops -S GEPDOT_2.c
GEPDOT_2 के बाद से पहले से ही सभी रजिस्टरों का इस्तेमाल किया, कोई unrolling किया जाता है।
जीसीसी संभावित से बचने के लिए नामांकन रजिस्टर करता है झूठी निर्भरता पेश की गई। लेकिन मुझे यकीन है कि मेरे जीईपीडीओटी में ऐसी कोई संभावना नहीं होगी; यहां तक कि यदि भी है, तो यह महत्वपूर्ण नहीं है। मैंने खुद को लूप को अनलॉक करने का प्रयास किया, और अनोलरिंग की तुलना में 4 गुना अनलोल करने से तेज 4 गुना तेज है। बेशक मैं मैन्युअल रूप से अधिक बार अनलॉक कर सकता हूं, लेकिन यह थकाऊ है। क्या जीसीसी मेरे लिए ऐसा कर सकता है? धन्यवाद।
// C file "GEPDOT.c"
#include <emmintrin.h>
void GEPDOT (double *A, double *B, double *C) {
__m128d A1_vec = _mm_load_pd(A); A += 2;
__m128d B_vec = _mm_load1_pd(B); B++;
__m128d C1_vec = A1_vec * B_vec;
__m128d A2_vec = _mm_load_pd(A); A += 2;
__m128d C2_vec = A2_vec * B_vec;
B_vec = _mm_load1_pd(B); B++;
__m128d C3_vec = A1_vec * B_vec;
__m128d C4_vec = A2_vec * B_vec;
B_vec = _mm_load1_pd(B); B++;
__m128d C5_vec = A1_vec * B_vec;
__m128d C6_vec = A2_vec * B_vec;
B_vec = _mm_load1_pd(B); B++;
__m128d C7_vec = A1_vec * B_vec;
A1_vec = _mm_load_pd(A); A += 2;
__m128d C8_vec = A2_vec * B_vec;
B_vec = _mm_load1_pd(B); B++;
int k = 58;
/* can compiler unroll the loop completely (i.e., peel this loop)? */
while (k--) {
C1_vec += A1_vec * B_vec;
A2_vec = _mm_load_pd(A); A += 2;
C2_vec += A2_vec * B_vec;
B_vec = _mm_load1_pd(B); B++;
C3_vec += A1_vec * B_vec;
C4_vec += A2_vec * B_vec;
B_vec = _mm_load1_pd(B); B++;
C5_vec += A1_vec * B_vec;
C6_vec += A2_vec * B_vec;
B_vec = _mm_load1_pd(B); B++;
C7_vec += A1_vec * B_vec;
A1_vec = _mm_load_pd(A); A += 2;
C8_vec += A2_vec * B_vec;
B_vec = _mm_load1_pd(B); B++;
}
C1_vec += A1_vec * B_vec;
A2_vec = _mm_load_pd(A);
C2_vec += A2_vec * B_vec;
B_vec = _mm_load1_pd(B); B++;
C3_vec += A1_vec * B_vec;
C4_vec += A2_vec * B_vec;
B_vec = _mm_load1_pd(B); B++;
C5_vec += A1_vec * B_vec;
C6_vec += A2_vec * B_vec;
B_vec = _mm_load1_pd(B);
C7_vec += A1_vec * B_vec;
C8_vec += A2_vec * B_vec;
/* [write-back] */
A1_vec = _mm_load_pd(C); C1_vec = A1_vec - C1_vec;
A2_vec = _mm_load_pd(C + 2); C2_vec = A2_vec - C2_vec;
A1_vec = _mm_load_pd(C + 4); C3_vec = A1_vec - C3_vec;
A2_vec = _mm_load_pd(C + 6); C4_vec = A2_vec - C4_vec;
A1_vec = _mm_load_pd(C + 8); C5_vec = A1_vec - C5_vec;
A2_vec = _mm_load_pd(C + 10); C6_vec = A2_vec - C6_vec;
A1_vec = _mm_load_pd(C + 12); C7_vec = A1_vec - C7_vec;
A2_vec = _mm_load_pd(C + 14); C8_vec = A2_vec - C8_vec;
_mm_store_pd(C,C1_vec); _mm_store_pd(C + 2,C2_vec);
_mm_store_pd(C + 4,C3_vec); _mm_store_pd(C + 6,C4_vec);
_mm_store_pd(C + 8,C5_vec); _mm_store_pd(C + 10,C6_vec);
_mm_store_pd(C + 12,C7_vec); _mm_store_pd(C + 14,C8_vec);
}
अद्यतन 1
@ user3386109 द्वारा टिप्पणी के लिए धन्यवाद, मैं इस सवाल का एक छोटा सा विस्तार करना चाहते हैं। @ user3386109 बहुत अच्छा प्रश्न उठाता है। असल में मुझे इष्टतम पंजीकरण आवंटन के लिए कंपाइलर की क्षमता पर कुछ संदेह है, जब शेड्यूल करने के लिए कई समानांतर निर्देश हैं।
मैं व्यक्तिगत रूप से लगता है कि एक विश्वसनीय तरीका पहले कोड पाश शरीर एएसएम इनलाइन विधानसभा में (जो एचपीसी के लिए महत्वपूर्ण है), तो यह नकल के रूप में कई बार के रूप में मैं चाहता हूँ के लिए है। मेरे पास इस साल की शुरुआत में एक अलोकप्रिय पोस्ट था: inline assembly। कोड थोड़ा अलग था क्योंकि लूप पुनरावृत्तियों की संख्या, जे, एक फ़ंक्शन तर्क है इसलिए संकलन समय पर अज्ञात है। उस स्थिति में मैं लूप को पूरी तरह से अनलॉक नहीं कर सकता, इसलिए मैंने केवल दो बार असेंबली कोड डुप्लिकेट किया, और लूप को एक लेबल में बदल दिया और कूद दिया।यह पता चला कि मेरी लिखित असेंबली का परिणामी प्रदर्शन कंपाइलर जेनरेट असेंबली से लगभग 5% अधिक है, जो यह सुझाव दे सकता है कि संकलक हमारे अपेक्षित, इष्टतम तरीके से रजिस्टरों को आवंटित करने में विफल रहता है।
मैं असेंबली कोडिंग में एक बच्चा था (और अभी भी) था, ताकि x86 असेंबली पर थोड़ा सा सीखने के लिए मेरे लिए एक अच्छा केस अध्ययन किया जा सके। लेकिन लंबे समय तक मैं असेंबली के लिए बड़े अनुपात के साथ जीईपीडीओटी कोड करने की इच्छा नहीं करता हूं।
- एएसएम इनलाइन विधानसभा पोर्टेबल नहीं होने के लिए critisized कर दिया गया है: वहाँ मुख्य रूप से तीन कारण हैं। हालांकि मुझे समझ में नहीं आता क्यों। शायद क्योंकि विभिन्न मशीनों के अलग-अलग रजिस्ट्रार हैं?
- कंपाइलर भी बेहतर हो रहा है। इसलिए मैं अभी भी अच्छा आउटपुट उत्पन्न करने में कंपाइलर की सहायता करने के लिए एल्गोरिदमिक ऑप्टिमाइज़ेशन और बेहतर सी कोडिंग आदत पसंद करूंगा;
- अंतिम कारण अधिक महत्वपूर्ण है। पुनरावृत्तियों की संख्या हमेशा 58 नहीं हो सकती है। मैं एक उच्च प्रदर्शन मैट्रिक्स कारकराइजेशन सबराउटिन विकसित कर रहा हूं। कैश अवरोधक कारक एनबी के लिए, पुनरावृत्तियों की संख्या (एनबी -2) होगी। मैं एनबी को फ़ंक्शन तर्क के रूप में नहीं डालूंगा, जैसा कि मैंने पहले की पोस्ट में किया था। यह एक मशीन विशिष्ट पैरामीटर एक मैक्रो के रूप में परिभाषित किया जाएगा। इसलिए पुनरावृत्तियों की संख्या संकलित समय पर ज्ञात है, लेकिन मशीन से मशीन में भिन्न हो सकती है। मान लीजिए कि मैन्युअल लूप में एनबी के लिए अनलॉकिंग में मुझे कितना कठिन काम करना है। तो अगर एक लूप छीलने के लिए संकलक को निर्देश देने का कोई तरीका है, तो यह बहुत अच्छा है।
यदि आप उच्च प्रदर्शन, अभी तक पोर्टेबल लाइब्रेरी बनाने में कुछ अनुभव साझा कर सकते हैं तो मुझे बहुत सराहना की जाएगी।
क्या आपने '-फुनोल-ऑल-लूप' का प्रयास किया था? –
तो यदि आप मैन्युअल रूप से उस लूप के शरीर को डुप्लिकेट करते हैं, तो क्या जीसीसी रजिस्टर उपयोग के प्रबंधन के लिए एक सभ्य नौकरी करता है? मैं पूछता हूं क्योंकि प्रीप्रोसेसर लिखने के लिए यह काफी आसान लगता है जो लूप को अनलॉक करेगा। उदाहरण के लिए, 'preproc__repeat (58) 'के साथ' while' को प्रतिस्थापित करें। फिर एक प्रीप्रोसेसर लिखें जो 'preproc__repeat' की खोज करता है, संख्या निकालता है, और शरीर को संकेतित समय की संख्या को डुप्लिकेट करता है। – user3386109
1) विभिन्न प्रोसेसर न केवल अलग रजिस्टरों को पकड़ते हैं। वे * एक ही रजिस्ट्रार * नहीं है। और उनके पास एक ही निर्देश नहीं हैं (हालांकि _mm_load1_pd कुछ हद तक प्रोसेसर विशिष्ट होने जा रहा है)। इसके अलावा, विभिन्न कंपाइलर इनलाइन एएसएम निर्देशों का अलग-अलग व्यवहार करते हैं। इनलाइन एएसएम जो एक कंपाइलर पर काम करता है संकलित कर सकता है, लेकिन दूसरे पर सही परिणाम उत्पन्न करने में विफल रहता है। –