2017-04-27 7 views
23

यहाँ कुछ कोड है जो जीसीसी 6 और 7 जब std::array का उपयोग कर अनुकूलन करने के लिए असफल है विफल रहता है:जीसीसी अनुकूलन करने के लिए गठबंधन std :: सी सरणी की तरह सरणी

#include <array> 

static constexpr size_t my_elements = 8; 

class Foo 
{ 
public: 
#ifdef C_ARRAY 
    typedef double Vec[my_elements] alignas(32); 
#else 
    typedef std::array<double, my_elements> Vec alignas(32); 
#endif 
    void fun1(const Vec&); 
    Vec v1{{}}; 
}; 

void Foo::fun1(const Vec& __restrict__ v2) 
{ 
    for (unsigned i = 0; i < my_elements; ++i) 
    { 
     v1[i] += v2[i]; 
    } 
} 

g++ -std=c++14 -O3 -march=haswell -S -DC_ARRAY साथ ऊपर संकलन का उत्पादन अच्छा कोड:

vmovapd ymm0, YMMWORD PTR [rdi] 
    vaddpd ymm0, ymm0, YMMWORD PTR [rsi] 
    vmovapd YMMWORD PTR [rdi], ymm0 
    vmovapd ymm0, YMMWORD PTR [rdi+32] 
    vaddpd ymm0, ymm0, YMMWORD PTR [rsi+32] 
    vmovapd YMMWORD PTR [rdi+32], ymm0 
    vzeroupper 

यह 256-बिट रजिस्टरों के माध्यम से एक समय में चार युगल जोड़ने का मूल रूप से दो अनियंत्रित पुनरावृत्तियों है। लेकिन अगर आप -DC_ARRAY बिना संकलन, तो आप इस के साथ शुरू एक विशाल गड़बड़ मिलती है:

mov  rax, rdi 
    shr  rax, 3 
    neg  rax 
    and  eax, 3 
    je  .L7 

कोड इस मामले में उत्पन्न (एक सादे सी सरणी के बजाय std::array का प्रयोग करके) इनपुट के संरेखण के लिए जाँच करने के लिए लगता है array-- भले ही यह टाइप किए गए टाइप में 32 बाइट्स के रूप में निर्दिष्ट है।

ऐसा लगता है कि जीसीसी यह नहीं समझता कि std::array की सामग्री std::array के समान ही गठबंधन है। यह धारणा को तोड़ता है कि सी arrays के बजाय std::array का उपयोग रनटाइम लागत नहीं लेता है।

क्या कुछ आसान है जो मुझे याद आ रही है जो इसे ठीक करेगी? अब तक मैं एक बदसूरत हैक के साथ आया था:

void Foo::fun2(const Vec& __restrict__ v2) 
{ 
    typedef double V2 alignas(Foo::Vec); 
    const V2* v2a = static_cast<const V2*>(&v2[0]); 

    for (unsigned i = 0; i < my_elements; ++i) 
    { 
     v1[i] += v2a[i]; 
    } 
} 

भी ध्यान रखें: यदि my_elements 4 के बजाय 8, समस्या नहीं होती है। यदि आप क्लैंग का उपयोग करते हैं, तो समस्या नहीं होती है।

आप इसे यहाँ रहते देख सकते हैं: दिलचस्प बात यह है https://godbolt.org/g/IXIOst

+4

Fwiw, बजना शिकायत है कि 'alignas', एक डेटा सदस्य पर होने की जरूरत है एक typedef पर नहीं है, लेकिन अगर बदलते' Vec' एक नेस्टेड क्लास होल्डिंग 'std :: array <...>' एक गठबंधन डेटा सदस्य के रूप में, और इसे 'ऑपरेटर []' अधिभार देता है, तो क्लैंग इसे अनुकूलित करने का प्रबंधन करता है। जीसीसी अभी भी नहीं है। – hvd

+3

क्या 'std :: array' के अंतर्गत सरणी में' std :: array' के समान संरेखण है? –

+1

और यदि 'वीईसी' को 'डबल डेटा [my_elements] alignas (32) धारण करने वाले वर्ग के रूप में लागू किया गया है,' कस्टम 'ऑपरेटर [] 'के साथ, तो जीसीसी इसे अनुकूलित करने का प्रबंधन करता है। मुझे संदेह है कि समस्या यह है कि 'सरणी :: ऑपरेटर [] 'एक असाइन किए गए' डबल 'को वापस लौटाता है जो कि इसके अनलाइन किए गए' सरणी :: _ M_elems' सदस्य से आता है, और तथ्य यह है कि यह एक गठबंधन सरणी का हिस्सा है, यह बहुत दूर है ऑप्टिमाइज़र इसे देखने में सक्षम होने के लिए। – hvd

उत्तर

17

, यदि आप v1._M_elems[i] += v2._M_elems[i]; साथ v1[i] += v2a[i]; की जगह (जो स्पष्ट रूप से पोर्टेबल नहीं है), जीसीसी std :: सरणी मामले अनुकूलन करने के लिए सी के मामले के साथ ही प्रबंधन करता है सरणी।

संभावित व्याख्या: जीसीसी डंप (-fdump-tree-all-all) में, कोई सी सरणी मामले में MEM[(struct FooD.25826 *)this_7(D) clique 1 base 0].v1D.25832[i_15] और MEM[(const value_typeD.25834 &)v2_7(D) clique 1 base 1][_1] std :: array के लिए देख सकता है। यही है, दूसरे मामले में, जीसीसी भूल गया होगा कि यह प्रकार फू का हिस्सा है और केवल याद करता है कि यह एक डबल तक पहुंच रहा है।

यह एक अबाउटमेंट पेनल्टी है जो सभी इनलाइन फ़ंक्शंस से आता है जिसे अंत में सरणी पहुंच देखने के लिए जाना पड़ता है। Clang अभी भी अच्छी तरह से vectorize करने के लिए प्रबंधन (alignas हटाने के बाद भी!)। इसका मतलब यह है कि संरेखण के बारे में देखभाल किए बिना क्लैंग वेक्टरेट करता है, और वास्तव में यह vmovupd जैसे निर्देशों का उपयोग करता है जिन्हें एक गठबंधन पते की आवश्यकता नहीं होती है।

वीक को कास्टिंग करने वाला हैक, संकलक को देखने के लिए एक और तरीका है, जब यह मेमोरी एक्सेस को संभालता है, तो जिस प्रकार से संभाला जा रहा है उसे गठबंधन किया जाता है। नियमित std :: array :: ऑपरेटर [] के लिए, स्मृति एक्सेस std :: array के सदस्य फ़ंक्शन के अंदर होती है, जिसमें कोई संकेत नहीं है कि *this गठबंधन होता है।

जीसीसी भी संकलक संरेखण के बारे में बताने के लिए एक builtin है:

const double*v2a=static_cast<const double*>(__builtin_assume_aligned(v2.data(),32)); 
+10

मैंने इसे यहां जीसीसी को बताया है: https://gcc.gnu.org/bugzilla/show_bug.cgi?id=80561 –

+3

बग रिपोर्ट दर्ज करने के लिए बहुत बहुत धन्यवाद :-) –

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