2016-11-16 16 views
6

मेरे पास एक ऐसा फ़ंक्शन है जो किसी दिए गए पूर्णांक के लिए दो की अगली शक्ति पाता है। यदि पूर्णांक दो की शक्ति है तो यह शक्ति देता है।सी ++ कंपाइलर "if (test) --foo" को "foo - = test" को अनुकूलित करने में विफल क्यों होता है?

सुंदर सीधे आगे:

char nextpow2if(int a) 
{ 
    char foo = char(32 - __builtin_clz(a)); 
    bool ispow2 = !(a & a-1); 
    if (ispow2) --foo; 
    return foo; 
} 

हालांकि, -O2 साथ जीसीसी 6 के साथ संकलन उत्पन्न विधानसभा निरीक्षण के बाद के बाद, मुझे लगता है कि इस foo-1 की गणना के बाद प्रतीत होता है बेकार अनुदेश cmovne साथ संकलित किया गया है। जीसीसी 5 और उससे अधिक उम्र के साथ भी बदतर मुझे कोड में वास्तविक jne शाखा मिलती है।

इस रूप में अगर मैं निम्नलिखित समारोह लिखा था होगा संकलित करने के लिए तेजी से रास्ता:

char nextpow2sub(int a) 
{ 
    char foo = char(32 - __builtin_clz(a)); 
    bool ispow2 = !(a & a-1); 
    return foo - ispow2; 
} 

इस कोड को सही ढंग से कम से कम (और सबसे तेजी से) एक sete और घटाव के साथ संभव विधानसभा के लिए सभी compilers द्वारा संकलित किया गया है बूल के लिए।

संकलक पहले को अनुकूलित करने में विफल क्यों होता है? यह पहचान मामले के लिए वास्तव में आसान लगता है। जीसीसी 5 और पुराने क्यों इसे वास्तविक jne शाखा में संकलित करते हैं? क्या दो संस्करणों के बीच कोई बढ़िया मामला है, जिसे मैं नहीं देख सकता, जिससे वे अलग-अलग व्यवहार कर सकते हैं?

पुनश्च: लाइव डेमो here

संपादित करें: मैं जीसीसी 6 के साथ लेकिन जीसीसी 5 के साथ बाद के बारे में दो गुना तेजी से (अच्छी तरह से एक कृत्रिम performanse परीक्षण पर, कम से कम) है प्रदर्शन का परीक्षण नहीं किया। यही कारण है कि वास्तव में मुझे इस सवाल पूछने के लिए प्रेरित किया।

+1

असंबद्ध भाषाओं के लिए स्पैम टैग न करें! – Olaf

+0

* "इसे संकलित करने का तेज़ तरीका ऐसा होगा जैसे मैंने निम्न कार्य लिखा था:" * क्या आपने इसे माप लिया? यह कितना तेज़ है? –

+0

क्या आप उत्पन्न असेंबली कोड की संख्या से प्रदर्शन की तुलना कर रहे हैं? यह करने का यह एक अच्छा तरीका नहीं है (हालांकि यह कुछ मामलों में सच हो सकता है)। – Arunmu

उत्तर

0

मेरा मानना ​​है कि इसका कारण यह हो सकता है कि bool आमतौर पर बाइट के भीतर संग्रहीत किया जाता है। इसलिए, संकलक वास्तविक स्मृति को सुरक्षित रूप से मानने में सक्षम नहीं हो सकता है बिल्कुल 1 के बराबर है। true/false शायद शून्य के विरुद्ध तुलना करता है। हालांकि, घटाव साइड इफेक्ट्स के साथ एक अलग कहानी हो सकती है।

example code on Ideone देखें:

#include <iostream> 
using namespace std; 

union charBool 
{ 
    unsigned char aChar; 
    bool aBool; 
}; 

int main() 
{ 
    charBool var; 
    charBool* varMemory = &var; 

    var.aBool = 65; 
    std::cout << "a boolean = " << var.aBool << std::endl; 
    std::cout << "a char = " << var.aChar << std::endl; 
    std::cout << "varMemory = " << (*(reinterpret_cast<unsigned char*>(varMemory))) << std::endl; 

    var.aChar = 98; // note: Ideone C++ compiler resolves this to zero, hence bit0 seems to be the only checked 
    std::cout << "a boolean = " << var.aBool << std::endl; 
    std::cout << "a char = " << var.aChar << std::endl; 
    std::cout << "varMemory = " << (*(reinterpret_cast<unsigned char*>(varMemory))) << std::endl; 

    return 0; 
} 

कि में परिणाम:

a boolean = 1 
a char = 
varMemory = 
a boolean = 0 
a char = b 
varMemory = b 

(ध्यान दें: पहले दो वर्ण गंदा कर रहे हैं)

+1

मुझे नहीं लगता कि यह कुछ भी कैसे जवाब देता है। तेजी से कोड को अनुकूलित करने का एकमात्र वास्तविक कारण इस तरह के नियमों के तहत दोनों प्रकारों के बीच कुछ अंतर खोजेगा। –

+0

इसके अलावा ओटी: 'यूनियन' के माध्यम से दंड टाइप करना यूबी है। –

+0

@ बामुमितएगेन संक्षेप में: बुलियन में शून्य के अलावा कुछ भी मानने के लिए सस्ता लगता है 'सत्य' है। यदि आप इसे "प्रदर्शन के लिए बिल्कुल 1" अनुकूलन को सक्षम करना चाहते हैं, तो पहले प्रदर्शन करने के लिए - आपको हमेशा दुष्प्रभावों की जांच करनी होगी (यानी यदि कोई = = 1 पर आधारित है)। उपरोक्त कोड एक बूलियन आंतरिक मेमोरी को संशोधित करने के लिए एक नाटक है (क्योंकि असाइनमेंट इसे 0/1 पर सेट कर सकता है)। – hauron

0

ठीक है, संकलक वास्तव में इस विशिष्ट में इस अनुकूलन प्रदर्शन कर सकता है मानक का उल्लंघन किए बिना मामला। लेकिन निम्न थोड़ा अलग मामले पर विचार:

char nextpow2sub(int a) 
{ 
    char foo = char(32 - __builtin_clz(a)); 
    bool ispow2 = !(a & a-1); 
    return foo - (5 * ispow2); 
} 

char nextpow2if(int a) 
{ 
    char foo = char(32 - __builtin_clz(a)); 
    bool ispow2 = !(a & a-1); 
    if (ispow2) foo = foo - 5; 
    return foo; 
} 

केवल परिवर्तन मैं यहाँ बनाया है कि मैं 5 के बजाय 1. द्वारा घटाकर करती हूं कि आप जीसीसी 6.x का उपयोग कर संकलन और तुलना करें, तो आप करेंगे देखना है कि जेनरेट बाइनरी कोड दोनों कार्यों के लिए एक ही आकार का है। मैं उम्मीद करता हूं कि वे दोनों एक ही प्रदर्शन को कम या कम करें।

इससे पता चलता है कि संकलक का अनुकूलन एल्गोरिदम सामान्य केस को संभालने के लिए डिज़ाइन किया गया है। उस ने कहा, यहां तक ​​कि 1 से घटाए जाने के मामले में, मैं उम्मीद करता हूं (जीसीसी 6.x का उपयोग करके) कि किसी भी आधुनिक प्रोसेसर पर प्रदर्शन में एक छोटा सा अंतर होगा जो निर्देश-स्तर समांतरता का समर्थन करता है और नामांकन रजिस्टर करता है।

इस कोड को सही ढंग से कम से कम (और सबसे तेजी से) संभव विधानसभा एक sete साथ और घटाव bool के लिए करने के लिए सभी compilers द्वारा संकलित किया गया है।

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

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