2014-10-10 8 views
7

मैं हाल ही में इस प्रश्न पूछा:एक uint8_t सरणी कैसे बनाएं जो सख्त एलियासिंग को कमजोर न करे?

Using this pointer causes strange deoptimization in hot loop

समस्या यह है कि मैं प्रकार uint8_t की एक सरणी के लिए लिख रहा था और संकलक यह है कि यह विधि का this सूचक के साथ उर्फ ​​सकता है (के प्रकार का इलाज किया था struct T*), क्योंकि void* और char* (= uint8_t*) हमेशा C++ में किसी भी अन्य सूचक को उपनाम कर सकते हैं। इस व्यवहार ने एक मिस्ड ऑप्टिमाइज़ेशन अवसर का कारण बना दिया। मैं निश्चित रूप से इससे बचना चाहता हूं। तो सवाल यह है: क्या मैं uint8_t सरणी घोषित कर सकता हूं जो सख्त एलियासिंग को लागू करता है, यानी, कि कंपाइलर किसी अन्य प्रकार के किसी भी सूचक के साथ कभी भी अलियाकृत नहीं होता है? यानी, मैं strict_uint8_t प्रकार की तरह कुछ ढूंढ रहा हूं जो विशेष अलियासिंग व्यवहार के साथ uint8_t है। क्या इसको हासिल करने के लिए कोई रास्ता है?

उदाहरण कोड यह दिखाने के लिए कि मेरा क्या मतलब है, अन्य प्रश्न और सरलीकृत से उधार लिया गया है।

enum strict_uint8_t : uint8_t {}; 

आप को बदलने में सक्षम होना चाहते हैं और:

struct T{ 
    uint8_t* target; 
    void unpack3bit(char* source, int size) { 
     while(size > 0){ 
      uint64_t t = *reinterpret_cast<uint64_t*>(source); 
      /** `this->target` cannot be cached in a register here but has 
       to be reloaded 16 times because the compiler 
       thinks that `this->target` could alias with `this` itself. 
       What I want is a special uint8_t type that does not trigger 
       this behaviour. */ 
      this->target[0] = t & 0x7; 
      this->target[1] = (t >> 3) & 0x7; 
      this->target[2] = (t >> 6) & 0x7; 
      this->target[3] = (t >> 9) & 0x7; 
      this->target[4] = (t >> 12) & 0x7; 
      this->target[5] = (t >> 15) & 0x7; 
      this->target[6] = (t >> 18) & 0x7; 
      this->target[7] = (t >> 21) & 0x7; 
      this->target[8] = (t >> 24) & 0x7; 
      this->target[9] = (t >> 27) & 0x7; 
      this->target[10] = (t >> 30) & 0x7; 
      this->target[11] = (t >> 33) & 0x7; 
      this->target[12] = (t >> 36) & 0x7; 
      this->target[13] = (t >> 39) & 0x7; 
      this->target[14] = (t >> 42) & 0x7; 
      this->target[15] = (t >> 45) & 0x7; 
      source+=6; 
      size-=6; 
      target+=16; 
     } 
} 
}; 
+0

क्या आप वाकई 'लक्ष्य + = 16' क्या करना चाहते हैं जी ++ के मामले में? फिर आप मूल टी :: लक्ष्य' सूचक खो देंगे। –

+1

मुझे यह मानना ​​है कि मुझे समझ में नहीं आता कि 'इस' (किसी भी अन्य चर के बजाय) के साथ अलियासिंग क्या करता है। मुझे यह भी यकीन नहीं है कि यह प्रासंगिक है या नहीं। वैसे भी निश्चित रूप से एक दिलचस्प सवाल है। –

+0

@ जोचिम पिलेबोर्ग: हाँ, यह ठीक है। "लक्ष्य लिखने के सिर" सूचक के रूप में 'लक्ष्य' के बारे में सोचें। 'लक्ष्य' बफर की शुरुआत कहीं और संग्रहीत की जाती है। इसके अलावा, यह कोड सिर्फ समस्या दिखाने के लिए है :)। – gexicide

उत्तर

4

आप आधार प्रकार uint8_t साथ एक निश्चित-आकार गणन उपयोग कर सकते हैं: अधिक जानकारी के लिए जुड़े हुए सवाल और उसके स्वीकार किए जाते हैं जवाब पढ़ें uint8_t पारदर्शी रूप से, आप परिवर्तित निर्माता और रूपांतरण ऑपरेटर के साथ एक struct में लपेट कर सकते हैं:

struct strict_uint8_t { 
    enum : uint8_t {} i; 
    strict_uint8_t(uint8_t i) : i{i} {} 
    operator uint8_t() const { return i; } 
}; 

थी रों जीसीसी और बजना में अलियासिंग pessimization खत्म करने के लिए प्रकट होता है: https://godbolt.org/g/9Ta98b

(ध्यान दें:। पिछले दृष्टिकोण, एक bitfield का उपयोग कर, बजना में जीसीसी में काम किया लेकिन नहीं)

+0

स्पष्टीकरण के लिए: (अन्यथा पूरी तरह से अनावश्यक) aliasing के लिए संभावित क्षमता को हटाने के लिए बिटफील्ड विनिर्देश आवश्यक है? –

+0

@ कोनराड रुडॉल्फ बिल्कुल। – ecatmur

+0

@ecatmur: क्या यह पोर्टेबल है? ऐसा लगता है कि 'जीसीसी' के साथ काम करता है। लेकिन 'clang' का उपयोग करके अपने लिंक को आजमाएं। ऐसा लगता है जैसे यह इस हैक खरीद नहीं होगा :)। यह अभी भी 16 गुना पुनः लोड करता है। – gexicide

0

दृश्य स्टूडियो में आप __declspec(restict) कार्यों के लिए उपयोग कर सकते हैं और __restrict कंपाइलर को बताने के लिए चर के लिए कि पॉइंटर उपनाम मुक्त है। मेरा मानना ​​है कि जीसीसी जैसे अन्य कंपाइलर्स में __restrict__ विशेषता है (लेकिन मुझे यकीन नहीं है)। अधिक जानकारी के लिए here

+0

मैंने 'gcc' में' __restrict__' की कोशिश की। यह ठीक संकलित लेकिन कुछ भी नहीं बदला। – gexicide

0

मेरा मानना ​​है कि यदि आप दोनों फ़ंक्शन को एक फ़ंक्शन के माध्यम से पास करते हैं तो पॉइंटर्स restrict के साथ घोषित किए जाते हैं तो आप एलियासिंग से छुटकारा पायेंगे। हालांकि यह गैर-मानक कंपाइलर एक्सटेंशन है, उदा।

#include <cstdint> 
#include <climits> 

struct T{ 
    uint8_t* target; 
    private: 
    void unpack3bit(char*__restrict__ source, int size, uint8_t*__restrict__ dst) { 
     while(size > 0){ 
      uint64_t t = *source; 
      dst[0] = t & 0x7; 
      dst[1] = (t >> 3) & 0x7; 
      dst[2] = (t >> 6) & 0x7; 
      dst[3] = (t >> 9) & 0x7; 
      dst[4] = (t >> 12) & 0x7; 
      dst[5] = (t >> 15) & 0x7; 
      dst[6] = (t >> 18) & 0x7; 
      dst[7] = (t >> 21) & 0x7; 
      dst[8] = (t >> 24) & 0x7; 
      dst[9] = (t >> 27) & 0x7; 
      dst[10] = (t >> 30) & 0x7; 
      dst[11] = (t >> 33) & 0x7; 
      dst[12] = (t >> 36) & 0x7; 
      dst[13] = (t >> 39) & 0x7; 
      dst[14] = (t >> 42) & 0x7; 
      dst[15] = (t >> 45) & 0x7; 
      source+=6; 
      size-=6; 
      target+=16; 
     } 
    } 
public: 
    void unpack3bit(char* source, int size) { 
     unpack3bit(source,size,this->target); 
    } 

}; 

void f(int i, T& t, char* source) { 
    t.unpack3bit(source, i); 
} 

ऑनलाइन:: http://goo.gl/SCjpL6

+1

ध्यान दें कि आपको अतिरिक्त विधि की आवश्यकता नहीं है। बस एक स्थानीय चर में 'this-> target' को कैशिंग करना पर्याप्त है। हालांकि, आपका सुझाव इस विशिष्ट कोड के लिए बस एक चलना है, न कि मेरे प्रश्न का उत्तर। बेशक, मैं स्थानीय चर में कैशिंग पॉइंटर्स द्वारा हमेशा इस मुद्दे के आसपास काम कर सकता हूं। लेकिन यह बोझिल और त्रुटि प्रवण है क्योंकि इसे भुलाया जाना आसान है। इसलिए, मैं एक प्रकार या कुछ तुलनीय खोज रहा हूं जिसे बिना चिंता किए बगैर बाइट एरे के लिए उपयोग किया जा सकता है। – gexicide

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