GCC यह क्यों नहीं मान सकता है कि std :: वेक्टर :: आकार इस लूप में नहीं बदलेगा?


14

मैंने एक सहकर्मी का दावा किया कि if (i < input.size() - 1) print(0);इस पाश में अनुकूलित किया जाएगा ताकि input.size()हर पुनरावृत्ति में न पढ़ा जाए , लेकिन यह पता चला है कि यह मामला नहीं है!

void print(int x) {
    std::cout << x << std::endl;
}

void print_list(const std::vector<int>& input) {
    int i = 0;
    for (size_t i = 0; i < input.size(); i++) {
        print(input[i]);
        if (i < input.size() - 1) print(0);
    }
}

संकलक एक्सप्लोरर के अनुसार gcc विकल्पों के साथ -O3 -fno-exceptionsहम वास्तव में input.size()प्रत्येक पुनरावृत्ति को पढ़ रहे हैं और leaघटाव का उपयोग कर रहे हैं!

        movq    0(%rbp), %rdx
        movq    8(%rbp), %rax
        subq    %rdx, %rax
        sarq    $2, %rax
        leaq    -1(%rax), %rcx
        cmpq    %rbx, %rcx
        ja      .L35
        addq    $1, %rbx

दिलचस्प है, जंग में यह अनुकूलन होता है। ऐसा लगता है कि iएक चर के साथ प्रतिस्थापित किया जाता है jजिसे प्रत्येक पुनरावृत्ति को घटाया जाता है, और परीक्षण i < input.size() - 1को कुछ के साथ बदल दिया जाता है j > 0

fn print(x: i32) {
    println!("{}", x);
}

pub fn print_list(xs: &Vec<i32>) {
    for (i, x) in xs.iter().enumerate() {
        print(*x);
        if i < xs.len() - 1 {
            print(0);
        }
    }
}

में संकलक एक्सप्लोरर प्रासंगिक विधानसभा इस तरह दिखता है:

        cmpq    %r12, %rbx
        jae     .LBB0_4

मैं जाँच की और मैं कर रहा हूँ यकीन है कि r12है xs.len() - 1और rbxकाउंटर है। इससे पहले एक के addलिए rbxऔर movपाश के बाहर में है r12

ऐसा क्यों है? ऐसा लगता है जैसे कि जीसीसी इनलाइन को सक्षम करने में सक्षम है size()और operator[]जैसा कि उसने किया, यह जानना चाहिए कि size()यह परिवर्तन नहीं करता है। लेकिन हो सकता है कि जीसीसी के ऑप्टिमाइज़र ने जज किया हो कि इसे एक चर में खींचने के लायक नहीं है? या हो सकता है कि कुछ अन्य संभावित दुष्प्रभाव हैं जो इस असुरक्षित बना देंगे - क्या कोई जानता है?


1
इसके अलावा printlnशायद एक जटिल तरीका है, संकलक को यह साबित करने में परेशानी हो सकती है कि printlnवेक्टर को म्यूट नहीं करता है।
मिंग डक

1
@MooDDuck: एक और थ्रेड डेटा-रेस UB होगा। संकलनकर्ता कर सकते हैं और यह मान है कि नहीं होता है। यहां समस्या नॉन-इनलाइन फ़ंक्शन कॉल को है cout.operator<<()। कंपाइलर को पता नहीं है कि इस ब्लैक-बॉक्स फ़ंक्शन को std::vectorवैश्विक से संदर्भ नहीं मिलता है ।
पीटर कॉर्डेस

@PeterCordes: आप कर रहे हैं सही है कि अन्य धागे एक स्टैंडअलोन स्पष्टीकरण, और की जटिलता नहीं हैं printlnया operator<<कुंजी है।
मूइंग डक

कंपाइलर इन बाहरी तरीकों के शब्दार्थ को नहीं जानता है।
user207421

जवाबों:


10

गैर-इनलाइन फ़ंक्शन कॉल को cout.operator<<(int)ऑप्टिमाइज़र के लिए एक ब्लैक बॉक्स है (क्योंकि लाइब्रेरी सिर्फ C ++ में लिखा गया है और सभी ऑप्टिमाइज़र देखता है कि एक प्रोटोटाइप है; टिप्पणियों में चर्चा देखें)। इसे किसी भी स्मृति को ग्रहण करना होगा जिसे संभवतः एक वैश्विक संस्करण द्वारा संशोधित किया जा सकता है।

(या std::endlकॉल। BTW, सिर्फ एक मुद्रण के बजाय उस बिंदु पर क्यूट का एक फ्लश क्यों मजबूर करता है '\n'?)

उदाहरण के लिए यह सभी जानते हैं, std::vector<int> &inputएक वैश्विक चर का एक संदर्भ है, और उन फ़ंक्शन कॉल में से एक उस वैश्विक संस्करण को संशोधित करता है । (या vector<int> *ptrकहीं एक वैश्विक है, या एक फ़ंक्शन है जो static vector<int>किसी अन्य संकलन इकाई में एक में एक सूचक लौटाता है , या किसी अन्य तरीके से एक फ़ंक्शन इस वेक्टर का संदर्भ प्राप्त कर सकता है, हमारे द्वारा इसका संदर्भ दिए बिना।

यदि आपके पास एक स्थानीय चर है जिसका पता कभी नहीं लिया गया था, तो संकलक मान सकता है कि गैर-इनलाइन फ़ंक्शन कॉल इसे म्यूट नहीं कर सकता है। क्योंकि इस ऑब्जेक्ट के लिए किसी भी ग्लोबल वैरिएबल का पॉइंटर रखने का कोई तरीका नहीं होगा। ( इसे एस्केप एनालिसिस कहा जाता है )। यही कारण है कि कंपाइलर size_t iफ़ंक्शन कॉल के दौरान एक रजिस्टर में रख सकता है । ( int iबस दूर अनुकूलित हो सकता है क्योंकि यह द्वारा छाया हुआ है size_t iऔर अन्यथा उपयोग नहीं किया जाता है)।

यह एक स्थानीय vector(आधार के लिए, एंड_साइज़ और एंड_स्कैस पॉइंटर्स के साथ) कर सकता है।

आईएसओ C99 इस समस्या के लिए एक समाधान है: int *restrict foo। कई C ++ समर्थन int *__restrict fooका वादा करता है कि उस मेमोरी को इंगित करने के fooलिए केवल उस पॉइंटर के माध्यम से एक्सेस किया जाता है। 2 सरणियों लेने वाले कार्यों में सबसे अधिक उपयोगी है, और आप संकलक का वादा करना चाहते हैं जो वे ओवरलैप नहीं करते हैं। तो यह कोड को जनरेट किए बिना ऑटो-वैरिफाई कर सकता है और उसके लिए एक फॉलबैक लूप चला सकता है।

ओपी टिप्पणियाँ:

जंग में एक गैर-परस्पर संदर्भ एक वैश्विक गारंटी है जो आपके पास एक संदर्भ (C ++ के बराबर restrict) के मूल्य को कोई और नहीं बदल रहा है

यही कारण है कि जंग इस अनुकूलन क्यों कर सकते हैं, लेकिन C ++ नहीं कर सकते।


अपने C ++ को ऑप्टिमाइज़ करना

जाहिर है आपको auto size = input.size();अपने फ़ंक्शन के शीर्ष पर एक बार उपयोग करना चाहिए , ताकि कंपाइलर जानता है कि यह एक लूप अपरिवर्तनीय है। C ++ कार्यान्वयन आपके लिए इस समस्या को हल नहीं करते हैं, इसलिए आपको इसे स्वयं करना होगा।

आपको "कंट्रोल ब्लॉक" const int *data = input.data();से डेटा पॉइंटर के भार को भी फहराना पड़ सकता है std::vector<int>यह दुर्भाग्यपूर्ण है कि अनुकूलन को बहुत ही गैर-मुहावरेदार स्रोत परिवर्तनों की आवश्यकता हो सकती है।

रस्ट एक बहुत अधिक आधुनिक भाषा है, जिसे कंपाइलर डेवलपर्स द्वारा सीखा जाने के बाद डिज़ाइन किया गया है कि कंपाइलरों के लिए अभ्यास में क्या संभव था। यह वास्तव में अन्य तरीकों से भी पता चलता है, जिनमें से, कुछ शांत सामानों को सीपीयू के माध्यम से उजागर करना भी शामिल है i32.count_ones, घुमाएँ, बिट-स्कैन, आदि। यह वास्तव में गूंगा है कि आईएसओ सी ++ अभी भी इनमें से किसी को भी नहीं दिखाता है, सिवाय इसके std::bitset::count()


1
यदि वेक्टर द्वारा मान लिया जाता है तो ओपी के कोड में अभी भी परीक्षण है। इसलिए भले ही GCC उस मामले में अनुकूलन कर सके, लेकिन ऐसा नहीं है।
अखरोट

1
मानक operator<<उन ऑपरेंड प्रकारों के व्यवहार को परिभाषित करता है ; इसलिए मानक C ++ में यह एक ब्लैक बॉक्स नहीं है और कंपाइलर मान सकता है कि यह दस्तावेज क्या कहता है। हो सकता है कि वे पुस्तकालय के गैरजरूरी व्यवहार को जोड़कर डेवलपर्स का समर्थन करना चाहते हों ...
MM

2
ऑप्टिमाइज़र को उस व्यवहार को खिलाया जा सकता है जो मानक जनादेश देता है, मेरा कहना है कि इस अनुकूलन की अनुमति मानक द्वारा दी जाती है, लेकिन संकलक विक्रेता आपके द्वारा बताए गए तरीके को लागू करने का विकल्प चुनता है और इस अनुकूलन को छोड़ देता है
MM

2
@MM यह यादृच्छिक वस्तु नहीं कहती है, मैंने कहा कि एक कार्यान्वयन परिभाषित वेक्टर है। मानक में ऐसा कुछ भी नहीं है जो कार्यान्वयन को परिभाषित वेक्टर के कार्यान्वयन से प्रतिबंधित करता है जो ऑपरेटर << एक संशोधित परिभाषित तरीके से इस वेक्टर को एक्सेस और संशोधित करने की अनुमति देता है। coutउपयोगकर्ता से परिभाषित वर्ग के एक ऑब्जेक्ट की अनुमति देता है जिसका streambufउपयोग धारा के साथ जुड़ा होना चाहिए cout.rdbuf। इसी प्रकार से प्राप्त एक वस्तु ostreamके साथ जुड़ा जा सकता है cout.tie
रॉस रिज

2
@PeterCordes - मैं स्थानीय वैक्टरों के बारे में इतना आश्वस्त नहीं होता: जैसे ही कोई भी सदस्य समारोह से बाहर हो जाता है, स्थानीय लोग प्रभावी रूप से बच गए हैं क्योंकि thisसूचक अंतर्निहित रूप से पारित हो गया है। यह प्रारंभिक रूप में निर्माणकर्ता के रूप में हो सकता है। पर विचार करें इस सरल पाश - मैं केवल जीसीसी मुख्य लूप (से जाँच की L34:करने के लिए jne L34), लेकिन यह निश्चित रूप से के रूप में यदि वेक्टर सदस्यों बच गए (प्रत्येक यात्रा स्मृति से उन्हें लोड) बर्ताव कर रही है।
मधुमक्खी पालन
हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.