2012-01-19 17 views
44

मैं अच्छे प्रदर्शन लाभ के साथ कुछ समय के लिए इंटेल के एसएसई इंट्रिनिक्स का उपयोग कर रहा हूं। इसलिए, मुझे उम्मीद है कि एवीएक्स इंट्रिनिक्स मेरे कार्यक्रमों को आगे बढ़ाएगा। दुर्भाग्यवश, यह अब तक मामला नहीं था। शायद मैं एक बेवकूफ गलती कर रहा हूं, इसलिए अगर कोई मेरी मदद कर सकता है तो मैं बहुत आभारी रहूंगा।एसएसई के बजाय एवीएक्स इंट्रिनिक्स का उपयोग गति में सुधार नहीं करता है - क्यों?

मैं उबंटू 11.10 का उपयोग जी ++ 4.6.1 के साथ करता हूं। मैंने

g++ simpleExample.cpp -O3 -march=native -o simpleExample 

परीक्षण प्रणाली में इंटेल i7-2600 सीपीयू के साथ अपना प्रोग्राम संकलित किया (नीचे देखें)।

यहां कोड है जो मेरी समस्या का उदाहरण है। अपने सिस्टम पर, मैं आउटपुट प्राप्त

98.715 ms, b[42] = 0.900038 // Naive 
24.457 ms, b[42] = 0.900038 // SSE 
24.646 ms, b[42] = 0.900038 // AVX 

ध्यान दें कि गणना sqrt (sqrt (sqrt (x))) केवल कि स्मृति बैंडविड्थ सुनिश्चित करने के लिए निष्पादन की गति को सीमित नहीं करता चुना गया था; यह सिर्फ एक उदाहरण है।

simpleExample.cpp:

#include <immintrin.h> 
#include <iostream> 
#include <math.h> 
#include <sys/time.h> 

using namespace std; 

// ----------------------------------------------------------------------------- 
// This function returns the current time, expressed as seconds since the Epoch 
// ----------------------------------------------------------------------------- 
double getCurrentTime(){ 
    struct timeval curr; 
    struct timezone tz; 
    gettimeofday(&curr, &tz); 
    double tmp = static_cast<double>(curr.tv_sec) * static_cast<double>(1000000) 
      + static_cast<double>(curr.tv_usec); 
    return tmp*1e-6; 
} 

// ----------------------------------------------------------------------------- 
// Main routine 
// ----------------------------------------------------------------------------- 
int main() { 

    srand48(0);   // seed PRNG 
    double e,s;   // timestamp variables 
    float *a, *b;   // data pointers 
    float *pA,*pB;   // work pointer 
    __m128 rA,rB;   // variables for SSE 
    __m256 rA_AVX, rB_AVX; // variables for AVX 

    // define vector size 
    const int vector_size = 10000000; 

    // allocate memory 
    a = (float*) _mm_malloc (vector_size*sizeof(float),32); 
    b = (float*) _mm_malloc (vector_size*sizeof(float),32); 

    // initialize vectors // 
    for(int i=0;i<vector_size;i++) { 
    a[i]=fabs(drand48()); 
    b[i]=0.0f; 
    } 

// +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 
// Naive implementation 
// +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 
    s = getCurrentTime(); 
    for (int i=0; i<vector_size; i++){ 
    b[i] = sqrtf(sqrtf(sqrtf(a[i]))); 
    } 
    e = getCurrentTime(); 
    cout << (e-s)*1000 << " ms" << ", b[42] = " << b[42] << endl; 

// ----------------------------------------------------------------------------- 
    for(int i=0;i<vector_size;i++) { 
    b[i]=0.0f; 
    } 
// ----------------------------------------------------------------------------- 

// +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 
// SSE2 implementation 
// +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 
    pA = a; pB = b; 

    s = getCurrentTime(); 
    for (int i=0; i<vector_size; i+=4){ 
    rA = _mm_load_ps(pA); 
    rB = _mm_sqrt_ps(_mm_sqrt_ps(_mm_sqrt_ps(rA))); 
    _mm_store_ps(pB,rB); 
    pA += 4; 
    pB += 4; 
    } 
    e = getCurrentTime(); 
    cout << (e-s)*1000 << " ms" << ", b[42] = " << b[42] << endl; 

// ----------------------------------------------------------------------------- 
    for(int i=0;i<vector_size;i++) { 
    b[i]=0.0f; 
    } 
// ----------------------------------------------------------------------------- 

// +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 
// AVX implementation 
// +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 
    pA = a; pB = b; 

    s = getCurrentTime(); 
    for (int i=0; i<vector_size; i+=8){ 
    rA_AVX = _mm256_load_ps(pA); 
    rB_AVX = _mm256_sqrt_ps(_mm256_sqrt_ps(_mm256_sqrt_ps(rA_AVX))); 
    _mm256_store_ps(pB,rB_AVX); 
    pA += 8; 
    pB += 8; 
    } 
    e = getCurrentTime(); 
    cout << (e-s)*1000 << " ms" << ", b[42] = " << b[42] << endl; 

    _mm_free(a); 
    _mm_free(b); 

    return 0; 
} 

किसी भी मदद की सराहना की है!

उत्तर

5

आपके प्रोसेसर हार्डवेयर के आधार पर, AVX निर्देश हार्डवेयर में एसएसई निर्देशों के रूप में नकल किए जा सकते हैं। आपको सटीक चश्मे प्राप्त करने के लिए अपने प्रोसेसर के भाग संख्या को देखने की आवश्यकता होगी, लेकिन यह निम्न अंत और उच्च अंत इंटेल प्रोसेसर के बीच मुख्य अंतरों में से एक है, विशेष रूप से निष्पादन इकाइयों बनाम हार्डवेयर इम्यूलेशन की संख्या।

+0

मुझे पता है कि AVX कभी नकल करते था नहीं था रुचि रखते हैं के लिए है - आप इस के लिए एक संदर्भ है? किस सीपीयू पर विशेष रूप से यह मामला होगा? –

+19

[निर्देश तालिका] (http://www.agner.org/optimize/instruction_tables.pdf) के अनुसार, सैंडी ब्रिज पर, पृष्ठ 87--88, ऐसा लगता है कि 'VDIVPS/PD' पोर्ट 0 पर 2 माइक्रोप्स निष्पादित करता है , 'DIVPS/PS' के लिए 1 माइक्रोप की तुलना में। 'एसक्यूआरटी 'निर्देश समान होंगे। चूंकि विभाजन इकाई पाइपलाइन नहीं है, इसलिए निष्पादन में 2x अधिक समय लगता है। यह इंगित करता है कि सैंडी ब्रिज में वास्तव में विभाजन इकाई का केवल 128-बिट कार्यान्वयन है। –

+0

@ नोर्बर्ट: स्पष्टीकरण के लिए धन्यवाद - मुझे –

42

यह वह जगह है VSQRTPS (AVX अनुदेश) एक सैंडी ब्रिज प्रोसेसर पर के रूप में SQRTPS (SSE अनुदेश) कई चक्र के रूप में वास्तव में दो बार ले जाता है क्योंकि। पेज instruction tables, 88.

निर्देश वर्गमूल और विभाजन की तरह AVX से लाभ नहीं मिलता: देखें Agner कोहरा का अनुकूलन गाइड। दूसरी तरफ, जोड़, गुणा, आदि, करते हैं।

9

आप वर्गमूल प्रदर्शन को बढ़ाने में रुचि रखते हैं, VSQRTPS के बजाय आप VRSQRTPS और न्यूटन- Raphson सूत्र का उपयोग कर सकते हैं:

x0 = vrsqrtps(a) 
x1 = 0.5 * x0 * (3 - (a * x0) * x0) 

VRSQRTPS ही AVX से लाभ नहीं होता है, लेकिन अन्य गणना करते हैं।

यदि आपके लिए सटीक 23 बिट्स पर्याप्त हैं तो इसका उपयोग करें।

6

बस पूर्णता के लिए। विभाजन या वर्ग रूट जैसे संचालन के लिए न्यूटन-रैफसन (एनआर) कार्यान्वयन केवल तभी फायदेमंद होगा यदि आपके पास आपके कोड में सीमित संख्या में ऑपरेशन हैं। ऐसा इसलिए है क्योंकि यदि आप इन वैकल्पिक तरीकों का उपयोग करते हैं तो आप गुणा और अतिरिक्त बंदरगाहों जैसे अन्य बंदरगाहों पर अधिक दबाव उत्पन्न करेंगे। यही कारण है कि x86 आर्किटेक्चर के पास वैकल्पिक सॉफ़्टवेयर समाधान (जैसे एनआर) की बजाय इन ऑपरेशन को संभालने के लिए विशेष हार्डवेयर इकाई है। मैंने Intel 64 and IA-32 Architectures Optimization Reference Manual से उद्धरण दिया है p.556:

"कुछ मामलों में, जब विभाजन या वर्ग रूट ऑपरेशन एक बड़े एल्गोरिदम का हिस्सा हैं जो इन परिचालनों की कुछ विलम्ब को छुपाता है, न्यूटन-रैफसन के साथ अनुमान निष्पादन धीमा कर सकता है । "

इसलिए बड़े एल्गोरिदम में एनआर का उपयोग करते समय सावधान रहें। असल में, मेरे पास इस बिंदु के आसपास मेरे मास्टर की थीसिस थी और इसे प्रकाशित होने के बाद, मैं भविष्य के संदर्भ के लिए यहां एक लिंक छोड़ दूंगा।

लोगों के लिए भी हमेशा थ्रूपुट और कुछ निर्देशों की विलंबता के बारे में आश्चर्य करते हैं, IACA पर एक नज़र डालें। इंटेल द्वारा कोड के इन-कोर निष्पादन प्रदर्शन का सांख्यिकीय रूप से विश्लेषण करने के लिए यह एक बहुत ही उपयोगी टूल है।

संपादित यहाँ थीसिस के लिए एक लिंक जो लोग thesis