2016-07-15 8 views
9

के संबंध में जीसीसी व्यवहार को परेशान करना प्रारंभ में #pragma omp simd निर्देश के प्रभाव की जांच कर रहा है, मैं एक ऐसे व्यवहार में आया जो मैं व्याख्या नहीं कर सकता, लूप के लिए सरल के वेक्टरलाइजेशन से संबंधित। निम्नलिखित कोड नमूना का परीक्षण इस भयानक compiler explorer पर किया जा सकता है, बशर्ते -O3 निर्देश लागू किया गया है और हम x86 आर्किटेक्चर पर हैं।वेक्टरनाइज़ेशन और लूप आकार

क्या कोई मुझे निम्नलिखित अवलोकनों के पीछे तर्क समझा सकता है?

#include <stdint.h> 

void test(uint8_t* out, uint8_t const* in, uint32_t length) 
{ 
    unsigned const l1 = (length * 32)/32; // This is vectorized 
    unsigned const l2 = (length/32)*32; // This is not vectorized 

    unsigned const l3 = (length << 5)>>5; // This is vectorized 
    unsigned const l4 = (length >> 5)<<5; // This is not vectorized 

    unsigned const l5 = length -length%32; // This is not vectorized 
    unsigned const l6 = length & ~(32 -1); // This is not vectorized 

    for (unsigned i = 0; i<l1 /*pick your choice*/; ++i) 
    { 
     out[i] = in[i*2]; 
    } 
} 

मुझे क्या पहेली है कि दोनों एल 1 और एल 3 beeing 32. के गुणकों और लेंथ नहीं vectorized कोड का उत्पादन करते हैं सब के सब होने की गारंटी नहीं के बावजूद vectorized कोड उत्पन्न है, लेकिन 32 के गुणकों होना चाहिए क्या इसके पीछे कोई कारण है?

एक तरफ, #pragma omp simd निर्देश का उपयोग वास्तव में कुछ भी नहीं बदलता है।

संपादित करें: आगे की जांच पड़ताल करने के बाद, व्यवहार के अंतर गायब हो जाता है जब सूचकांक प्रकार size_t है (और कोई सीमा हेरफेर भी जरूरत है), जिसका अर्थ है कि इस vectorized कोड उत्पन्न करता है: अगर किसी को पता है क्यों

#include <stdint.h> 
#include <string> 

void test(uint8_t* out, uint8_t const* in, size_t length) 
{ 
    for (size_t i = 0; i<length; ++i) 
    { 
     out[i] = in[i*2]; 
    } 
} 

लूप वेक्टरिंग इंडेक्स प्रकार पर इतना निर्भर है, तो मुझे और जानना उत्सुक होगा!

EDIT2, मार्क Lakata करने के लिए धन्यवाद, ओ 3 वास्तव में जरूरत है

+0

इस प्रश्न के विस्तार के रूप में क्या देखा जा सकता है, सटीक वही व्यवहार क्लैंग के साथ दिखाई देता है, इसलिए मुझे लगता है कि इसमें कुछ तर्क है। –

+1

ऐसा लगता है कि संकलक डरता है कि सूचकांक लपेट सकता है और इसके कारण छोड़ देता है :-( –

+0

प्रकार निर्भरता मुझे समझाया गया है, ओवरफ्लो के जोखिम से जुड़ा हुआ है (जो वेक्टरेशन को रोकता है)। एक हस्ताक्षरित ओवरफ्लो की अनुमति है , जबकि एक हस्ताक्षरित ओवरफ्लो नहीं है, जो इस आखिरी बिंदु को बताता है। पहले हस्ताक्षर का उपयोग करना और प्रभावी रूप से अतिप्रवाह जोखिम को मारना) वेक्टरेशन के लिए अनुमति देता है, जीसीसी सुपर स्मार्ट है: https://godbolt.org/g/SsVZ2r –

उत्तर

4

मुद्दा सरणी सूचकांक में size_t को unsigned से स्पष्ट रूपांतरण है: in[i*2];

आप l1 या l3 तो गणना का उपयोग करते हैं i*2 का size_t प्रकार में हमेशा फिट होगा। इसका अर्थ यह है कि प्रकार unsigned व्यावहारिक रूप से व्यवहार करता है जैसे कि यह size_t था।

लेकिन जब आप अन्य विकल्पों का उपयोग करते हैं, तो गणना i*2 का परिणाम संभवतः size_t में फिट नहीं हो सकता है क्योंकि मान लपेट सकता है और रूपांतरण किया जाना चाहिए।

अगर आप पहला उदाहरण लेते, विकल्प चुनने नहीं एल 1 या L3, और कलाकारों कार्य करें:

out[i] = in[(size_t)i*2]; 

संकलक का अनुकूलन, अगर आप पूरे अभिव्यक्ति डाली:

out[i] = in[(size_t)(i*2)]; 

ऐसा नहीं 'टी।


स्टैंडर्ड वास्तव में निर्दिष्ट नहीं है कि सूचकांक में प्रकार size_t होना चाहिए, लेकिन यह संकलक के नजरिए से एक तार्किक कदम है।

+0

मुझे यकीन नहीं है कि पॉइंटर को डिफ्रेंस करते समय सूचकांक 'size_t' में परिवर्तित हो जाते हैं, हालांकि आप जिस ओवरफ्लो के बारे में बात करते हैं, वह अभी भी प्रासंगिक है – SirGuy

+0

@GuyGreer मानक के मुताबिक, वे अपडेट नहीं देखते हैं। – 2501

+1

मैं अभी भी 'unsigned' से' size_t' (जो 32 बिट मशीन पर नो-ऑप) से किसी भी प्रकार के रूपांतरण में होने वाली समस्या से असहमत है। लपेटने से निपटने के मामले में आपका जवाब मुझे और अधिक समझ में आता है और जब संकलक स्वयं साबित कर सकता है कि ऐसा नहीं होगा। – SirGuy

1

मुझे विश्वास है कि आप वेक्टरेशन के साथ अनुकूलन को भ्रमित कर रहे हैं। मैंने आपके compiler explorer और x86 के लिए सेट-ओ 2 का उपयोग किया, और इनमें से कोई भी उदाहरण "वेक्टरकृत" नहीं है।

यहाँ l1

test(unsigned char*, unsigned char const*, unsigned int): 
     xorl %eax, %eax 
     andl $134217727, %edx 
     je  .L1 
.L5: 
     movzbl (%rsi,%rax,2), %ecx 
     movb %cl, (%rdi,%rax) 
     addq $1, %rax 
     cmpl %eax, %edx 
     ja  .L5 
.L1: 
     rep ret 

यहाँ है l2

test(unsigned char*, unsigned char const*, unsigned int): 
     andl $-32, %edx 
     je  .L1 
     leal -1(%rdx), %eax 
     leaq 1(%rdi,%rax), %rcx 
     xorl %eax, %eax 
.L4: 
     movl %eax, %edx 
     addq $1, %rdi 
     addl $2, %eax 
     movzbl (%rsi,%rdx), %edx 
     movb %dl, -1(%rdi) 
     cmpq %rcx, %rdi 
     jne  .L4 
.L1: 
     rep ret 

आश्चर्य की बात नहीं है यही कारण है, क्योंकि तुम क्या कर रहे अनिवार्य रूप से, एक "इकट्ठा" लोड ऑपरेशन है जहां लोड सूचकांक नहीं हैं स्टोर सूचकांक के समान। इकट्ठा/स्कैटर के लिए x86 में कोई समर्थन नहीं है। यह केवल AVX2 और AVX512 में पेश किया गया है, और यह चयनित नहीं है।

थोड़ा लंबा कोड हस्ताक्षरित/हस्ताक्षरित मुद्दों से निपट रहा है, लेकिन कोई वेक्टरेशन चालू नहीं है।

+0

वेक्टरेशन को स्पष्ट करने के लिए धन्यवाद। क्या आप हस्ताक्षरित/हस्ताक्षरित पर विस्तृत कर सकते हैं। सी स्रोत में कोई हस्ताक्षरित प्रकार नहीं है, तो वे असेंबली में क्यों उपयोग किए जाएंगे? – 2501

+0

अच्छी तरह से, मुझे वास्तव में पता नहीं है, लेकिन मुझे लगता है कि इसे अप्रत्यक्ष लोड 'movzbl (% rsi,% rax, 2),% ecx' की सीमाओं के साथ करना है और वह% रैक्स 32 से कम होना चाहिए बिट्स अन्यथा 2 का स्तर बह जाएगा। लेकिन मुझे Google पर तुरंत जवाब नहीं मिल सका ... –

+0

fwiw, आपके कोड में एक हस्ताक्षरित मूल्य है। निरंतर 2 पर हस्ताक्षर किए गए हैं ... लेकिन इससे इस चर्चा में कोई फर्क नहीं पड़ता। –

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