2011-10-27 21 views
13

यह पोस्ट some days ago पर पोस्ट किए गए किसी अन्य से निकटता से संबंधित है। इस बार, मैंने एक साधारण कोड लिखा था जो तत्वों के सरणी की एक जोड़ी जोड़ता है, परिणाम को किसी अन्य सरणी में मानों से गुणा करता है और इसे आगे सरणी में संग्रहीत करता है, सभी वैरिएबल फ़्लोटिंग पॉइंट डबल परिशुद्धता टाइप किया जाता है।जीसीसी एसएसई कोड अनुकूलन

मैंने उस कोड के दो संस्करण बनाए: एक एसएसई निर्देशों के साथ, कॉल का उपयोग करके और उनके बिना किसी अन्य के बाद मैंने उन्हें gcc और -O0 अनुकूलन स्तर के साथ संकलित किया। मैं उन्हें नीचे लिखें:

// SSE VERSION 

#define N 10000 
#define NTIMES 100000 
#include <time.h> 
#include <stdio.h> 
#include <xmmintrin.h> 
#include <pmmintrin.h> 

double a[N] __attribute__((aligned(16))); 
double b[N] __attribute__((aligned(16))); 
double c[N] __attribute__((aligned(16))); 
double r[N] __attribute__((aligned(16))); 

int main(void){ 
    int i, times; 
    for(times = 0; times < NTIMES; times++){ 
    for(i = 0; i <N; i+= 2){ 
     __m128d mm_a = _mm_load_pd(&a[i]); 
     _mm_prefetch(&a[i+4], _MM_HINT_T0); 
     __m128d mm_b = _mm_load_pd(&b[i]); 
     _mm_prefetch(&b[i+4] , _MM_HINT_T0); 
     __m128d mm_c = _mm_load_pd(&c[i]); 
     _mm_prefetch(&c[i+4] , _MM_HINT_T0); 
     __m128d mm_r; 
     mm_r = _mm_add_pd(mm_a, mm_b); 
     mm_a = _mm_mul_pd(mm_r , mm_c); 
     _mm_store_pd(&r[i], mm_a); 
     } 
    } 
} 

//NO SSE VERSION 
//same definitions as before 
int main(void){ 
    int i, times; 
    for(times = 0; times < NTIMES; times++){ 
    for(i = 0; i < N; i++){ 
     r[i] = (a[i]+b[i])*c[i]; 
    } 
    } 
} 

जब उन्हें -O0 साथ संकलन, जीसीसी, XMM/MMX रजिस्टर और SSE intstructions का उपयोग करता है नहीं तो विशेष रूप से -mno-SSE (और अन्य) विकल्प दिए। मैंने दूसरे कोड के लिए उत्पन्न असेंबली कोड का निरीक्षण किया और मैंने देखा कि यह movsd, जोड़ और mulsd निर्देशों का उपयोग करता है। इसलिए यह एसएसई निर्देशों का उपयोग करता है, लेकिन केवल उन लोगों में से जो रजिस्टरों के निम्नतम भाग का उपयोग करते हैं, अगर मैं गलत नहीं हूं। पहले सी कोड के लिए जेनरेट किया गया असेंबली कोड एडीपी और mulpd निर्देशों की अपेक्षा के अनुसार उपयोग किया गया, हालांकि एक बहुत बड़ा असेंबली कोड उत्पन्न हुआ था।

वैसे भी, पहला कोड बेहतर लाभ प्राप्त करना चाहिए, जहां तक ​​मुझे पता है, सिम प्रतिमान के, क्योंकि प्रत्येक पुनरावृत्ति दो परिणाम मानों की गणना की जाती है। फिर भी, दूसरा कोड कुछ ऐसा करता है जैसे कि पहले की तुलना में 25 प्रतिशत तेज। मैंने एकल परिशुद्धता मानों के साथ एक परीक्षण भी किया और इसी तरह के परिणाम प्राप्त किए। इसके लिए क्या कारण है?

+5

ऑप्टिमाइज़ेशन के बिना संकलन करते समय प्रदर्शन की तुलना करना काफी अर्थहीन है। – interjay

+1

आप केवल 2 एक्स अंकगणितीय परिचालनों के लिए 3 एक्स लोड और 1 एक्स स्टोर कर रहे हैं, इसलिए आप बैंडविड्थ-सीमित होने की संभावना अधिकतर होंगे। –

+5

क्या होता है जब आप _mm_prefetch कॉल को हटाते हैं? मुझे लगता है कि वे आपको – TJD

उत्तर

14

जीसीसी में वेक्टरेशन -O3 पर सक्षम है। यही कारण है कि -O0 पर, आप केवल सामान्य स्केलर एसएसई 2 निर्देश (movsd, addsd आदि) देखते हैं। जीसीसी 4.6.1 और अपने दूसरे उदाहरण का उपयोग करना:

#define N 10000 
#define NTIMES 100000 

double a[N] __attribute__ ((aligned (16))); 
double b[N] __attribute__ ((aligned (16))); 
double c[N] __attribute__ ((aligned (16))); 
double r[N] __attribute__ ((aligned (16))); 

int 
main (void) 
{ 
    int i, times; 
    for (times = 0; times < NTIMES; times++) 
    { 
     for (i = 0; i < N; ++i) 
     r[i] = (a[i] + b[i]) * c[i]; 
    } 

    return 0; 
} 

और gcc -S -O3 -msse2 sse.c साथ संकलन भीतरी पाश निम्नलिखित निर्देशों के लिए पैदा करता है, जो बहुत अच्छी है:

.L3: 
    movapd a(%eax), %xmm0 
    addpd b(%eax), %xmm0 
    mulpd c(%eax), %xmm0 
    movapd %xmm0, r(%eax) 
    addl $16, %eax 
    cmpl $80000, %eax 
    jne .L3 

आप देख सकते हैं vectorization साथ सक्षम जीसीसी समानांतर में दो लूप पुनरावृत्तियों को करने के लिए कोड उत्सर्जित करता है। यह सुधार किया जा सकता है, हालांकि - यह कोड एसएसई रजिस्टरों के निचले 128 बिट्स का उपयोग करता है, लेकिन यह एसएसई निर्देशों (यदि मशीन पर उपलब्ध हो) के AVX एन्कोडिंग को सक्षम करके 256-बिट वाईएमएम रजिस्टरों को पूरा कर सकता है।

.L3: 
    vmovapd a(%eax), %ymm0 
    vaddpd b(%eax), %ymm0, %ymm0 
    vmulpd c(%eax), %ymm0, %ymm0 
    vmovapd %ymm0, r(%eax) 
    addl $32, %eax 
    cmpl $80000, %eax 
    jne .L3 

कि नोट प्रत्येक निर्देश के सामने और उस v निर्देश 256-बिट YMM पंजीकृत करता है का उपयोग करें, चार मूल पाश की पुनरावृत्तियों क्रियान्वित कर रहे हैं: तो, gcc -S -O3 -msse2 -mavx sse.c के साथ एक ही कार्यक्रम संकलन भीतरी पाश के लिए देता है समान्तर में।

+0

मैंने इसे 'x86-64' पर 'gcc 4.7.2' के माध्यम से' -msse2' झंडे के साथ और बिना चलाया - दोनों के परिणामस्वरूप एक ही असेंबलर आउटपुट हुआ। तो क्या इस प्लेटफ़ॉर्म पर डिफ़ॉल्ट रूप से एसएसई निर्देशों को सक्षम करना सुरक्षित होगा? –

+0

@lori, हाँ, एसएसई x86-64 पर डिफ़ॉल्ट है। – chill

+0

यहां आप विभिन्न कंपाइलर्स के साथ जांच सकते हैं http://goo.gl/bM62CZ – KindDragon

2

मैं chill's answer का विस्तार करना चाहता हूं और इस तथ्य पर आपका ध्यान आकर्षित करना चाहता हूं कि जीसीसी पिछली बार फिर से चलने पर AVX निर्देशों का एक ही स्मार्ट उपयोग करने में सक्षम नहीं है।

for (i = N-1; i >= 0; --i) 
    r[i] = (a[i] + b[i]) * c[i]; 

जीसीसी (4.8:

बस के साथ ठंड का नमूना कोड में भीतरी पाश की जगह।4) विकल्पों के साथ -S -O3 -mavx उत्पादन:

.L5: 
    vmovsd a+79992(%rax), %xmm0 
    subq $8, %rax 
    vaddsd b+80000(%rax), %xmm0, %xmm0 
    vmulsd c+80000(%rax), %xmm0, %xmm0 
    vmovsd %xmm0, r+80000(%rax) 
    cmpq $-80000, %rax 
    jne  .L5 
+0

दिलचस्प। नए जीसीसी ऑटो-वेक्टरिज़ जो कि प्रत्येक सरणी इनपुट/आउटपुट के लिए लोड करने के बाद इसे उलट करने के लिए 'vpermpd 0b00011011' का उपयोग करके उल्लसित रूप से, प्रत्येक वेक्टर के भीतर डेटा तत्व स्रोत क्रम में पहले से अंतिम तक जाते हैं। यह 4 'vpermpd प्रति पुनरावृत्ति है! दिलचस्प बात यह है कि [क्लैंग ऑटो-वेक्टरिज़ेस अच्छी तरह से] [https://godbolt.org/g/azbIIi) –

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