जो, यदि कोई हो, C ++ कंपाइलर पूंछ-पुनरावृत्ति अनुकूलन करते हैं?


150

यह मुझे लगता है कि यह सी और सी ++ दोनों में पूंछ-पुनरावृत्ति अनुकूलन करने के लिए पूरी तरह से अच्छी तरह से काम करेगा, फिर भी डिबगिंग के दौरान मुझे कभी भी एक फ्रेम स्टैक नहीं दिखता है जो इस अनुकूलन को इंगित करता है। यह अच्छा है, क्योंकि स्टैक मुझे बताता है कि रिकर्सन कितना गहरा है। हालाँकि, अनुकूलन एक तरह से अच्छा होगा।

क्या कोई C ++ कंपाइलर इस ऑप्टिमाइज़ेशन को करता है? क्यों? क्यों नहीं?

मैं इसे करने के लिए संकलक को कैसे बताऊं?

  • MSVC के लिए: /O2या/Ox
  • जीसीसी के लिए: -O2या-O3

कैसे की जाँच करें कि संकलक ने एक निश्चित मामले में ऐसा किया है?

  • MSVC के लिए, PDB आउटपुट को कोड का पता लगाने में सक्षम करें, फिर कोड का निरीक्षण करें
  • GCC के लिए ..?

मैं अब भी सुझाव दूंगा कि कैसे निर्धारित किया जाए कि एक निश्चित फ़ंक्शन को कंपाइलर द्वारा इस तरह से ऑप्टिमाइज़ किया गया है (भले ही मुझे यह आश्वस्त लगता है कि कोनराड मुझे यह मानने के लिए कहता है)

यह जांचना हमेशा संभव होता है कि क्या कंपाइलर अनंत पुनरावृत्ति करके यह जाँचता है कि क्या यह अनन्त लूप या स्टैक ओवरफ्लो में परिणाम देता है या नहीं (मैंने जीसीसी के साथ ऐसा किया और पता चला कि -O2यह पर्याप्त है), लेकिन मैं बनना चाहता हूं एक निश्चित फ़ंक्शन की जांच करने में सक्षम जो मुझे पता है कि वैसे भी समाप्त हो जाएगा। मैं इस जाँच का एक आसान तरीका है प्यार करता हूँ :)


कुछ परीक्षण के बाद, मुझे पता चला कि विनाशकारी इस अनुकूलन को बनाने की संभावना को बर्बाद करते हैं। यह कभी-कभी इसके लायक हो सकता है कि रिटर्न-स्टेटमेंट शुरू होने से पहले वे कुछ वैरिएबल और टेम्पररी की स्कोपिंग को बदल दें और सुनिश्चित करें कि वे दायरे से बाहर हो जाएं।

यदि टेल-कॉल के बाद किसी भी विध्वंसक को चलाने की आवश्यकता है, तो टेल-कॉल ऑप्टिमाइज़ेशन नहीं किया जा सकता है।

जवाबों:


129

सभी वर्तमान मुख्यधारा कंपाइलर टेल कॉल ऑप्टिमाइज़ेशन को काफी अच्छी तरह से करते हैं (और एक दशक से अधिक समय तक किया है ), यहाँ तक कि पारस्परिक रूप से पुनरावृत्ति के लिए भी :

int bar(int, int);

int foo(int n, int acc) {
    return (n == 0) ? acc : bar(n - 1, acc + 2);
}

int bar(int n, int acc) {
    return (n == 0) ? acc : foo(n - 1, acc + 1);
}

कंपाइलर को ऑप्टिमाइज़ करने देना सीधा है: गति के लिए ऑप्टिमाइज़ेशन पर स्विच करें:

  • MSVC के लिए, का उपयोग करें /O2या /Ox
  • जीसीसी, क्लैंग और आईसीसी के लिए, का उपयोग करें -O3

यह जांचने का एक आसान तरीका है कि यदि कंपाइलर ने ऑप्टिमाइज़ेशन किया है तो एक कॉल करना है जो अन्यथा स्टैक ओवरफ्लो में परिणाम देगा - या असेंबली आउटपुट को देखकर।

एक दिलचस्प ऐतिहासिक नोट के रूप में, मार्क प्रोब्स्ट द्वारा डिप्लोमा थीसिस के दौरान सी के लिए टेल कॉल ऑप्टिमाइज़ेशन को जीसीसी में जोड़ा गया था । थीसिस कार्यान्वयन में कुछ दिलचस्प कैवेट का वर्णन करता है। यह पढ़ने लायक है।


आईसीसी ऐसा करेगा, मुझे विश्वास है। मेरी जानकारी के अनुसार, ICC बाजार में सबसे तेज कोड का उत्पादन करता है।
पॉल नाथन

35
@Paul सवाल यह है कि ICC कोड की गति एल्गोरिदम ऑप्टिमाइज़ेशन जैसे टेल कॉल ऑप्टिमाइज़ेशन और कैश और माइक्रोइन्स्ट्रक्शन ऑप्टिमाइज़ेशन के कारण कितनी होती है, जो केवल इंटेल, अपने स्वयं के प्रोसेसर के अंतरंग ज्ञान के साथ कर सकते हैं।
इमेजिस्ट

6
gcc-foptimize-sibling-calls"सिबलिंग और पूंछ पुनरावर्ती कॉल का अनुकूलन करने के लिए" अधिक संकीर्ण विकल्प है। यह (के अनुसार विकल्प gcc(1)संस्करणों 4.4, 4.7 और 4.8 विभिन्न प्लेटफार्मों लक्षित करने के लिए मैनुअल पृष्ठों) के स्तर पर सक्षम किया गया है -O2, -O3, -Os
फूफ

इसके अलावा, स्पष्ट रूप से अनुकूलन का अनुरोध किए बिना DEBUG मोड में चलने से कोई अनुकूलन नहीं होगा। आप वास्तविक रिलीज़ मोड EXE के लिए PDB को सक्षम कर सकते हैं और उसके माध्यम से कदम बढ़ाने का प्रयास कर सकते हैं, लेकिन ध्यान दें कि रिलीज़ मोड में डीबगिंग की जटिलताएँ हैं - अदृश्य / स्ट्रिप्ड वैरिएबल, मर्ज किए गए वैरिएबल, वैरिएबल अनजाने / अनपेक्षित स्कोप से दायरे से बाहर हो रहे हैं, वैरिएबल कभी भी अंदर नहीं जाते हैं। स्कोप-स्तरीय पतों के साथ गुंजाइश और सत्य स्थिरांक बन गए, और - अच्छी तरह से - मर्ज किए गए या लापता स्टैक फ़्रेम। आमतौर पर मर्ज किए गए स्टैक फ्रेम्स का मतलब है कि कैली इनलेट है, और लापता / बैकमरेटेड फ्रेम शायद टेल कॉल।
Пет15р Петров

21

gcc 4.3.2 पूरी तरह से इस फ़ंक्शन (भद्दा / तुच्छ atoi()कार्यान्वयन) को बताता है main()। अनुकूलन स्तर है -O1। मैं अगर मैं (इसके साथ चारों ओर खेलने भी से इसे बदलने नोटिस staticकरने के लिए extern, पूंछ प्रत्यावर्तन दूर बहुत तेजी से चला जाता है, तो मैं कार्यक्रम शुद्धता के लिए उस पर निर्भर नहीं होता।

#include <stdio.h>
static int atoi(const char *str, int n)
{
    if (str == 0 || *str == 0)
        return n;
    return atoi(str+1, n*10 + *str-'0');
}
int main(int argc, char **argv)
{
    for (int i = 1; i != argc; ++i)
        printf("%s -> %d\n", argv[i], atoi(argv[i], 0));
    return 0;
}

1
यद्यपि आप लिंक-टाइम ऑप्टिमाइज़ेशन को सक्रिय कर सकते हैं और मुझे लगता है कि एक externविधि भी इनलाइन हो सकती है।
कोनराड रुडोल्फ

5
अजीब। मैं सिर्फ जीसीसी 4.2.3 (x86, स्लैकवेयर 12.1) और जीसीसी 4.6.2 (AMD64, डेबियन खरखरा) और परीक्षण के साथ-O1 नहीं है कोई इनलाइन किए जाने वाले और कोई पूंछ-प्रत्यावर्तन अनुकूलन । आपको इसके लिए उपयोग -O2करना होगा (अच्छी तरह से, 4.2.x में, जो अब प्राचीन नहीं है, फिर भी यह इनलेट नहीं होगा)। BTW यह जोड़ने के लायक भी है कि जीसीसी पुनरावृत्ति का अनुकूलन कर सकता है, जबकि यह कड़ाई से पूंछ वाला नहीं है (जैसे कि फैक्टरियल डब्ल्यू / ओ संचयकर्ता)।
przemoc

16

साथ ही स्पष्ट (कंपाइलर इस प्रकार का अनुकूलन तब तक नहीं करते हैं जब तक कि आप इसके लिए नहीं पूछते हैं), सी ++ में टेल-कॉल ऑप्टिमाइज़ेशन के बारे में एक जटिलता है: विनाशकारी।

कुछ इस तरह दिया:

   int fn(int j, int i)
   {
      if (i <= 0) return j;
      Funky cls(j,i);
      return fn(j, i-1);
   }

कंपाइलर (सामान्य रूप से) टेल-कॉल इसे ऑप्टिमाइज़ नहीं कर सकता है क्योंकि इसे पुनरावर्ती कॉल रिटर्न के cls बाद विध्वंसक को कॉल करने की आवश्यकता होती है।

कभी-कभी कंपाइलर यह देख सकता है कि विध्वंसक के कोई बाहरी रूप से दिखाई देने वाले दुष्प्रभाव नहीं हैं (इसलिए इसे जल्दी किया जा सकता है), लेकिन अक्सर यह नहीं हो सकता।

इसका एक विशेष रूप से सामान्य रूप वह है जहां Funkyवास्तव में एक std::vectorया समान है।


मेरे लिए काम नहीं करता है। सिस्टम मुझे बताता है कि जब तक जवाब संपादित नहीं किया जाता, तब तक मेरा वोट बंद है।
21

बस जवाब को संपादित किया (परांठे हटा दिया) और अब मैं अपने पतन को पूर्ववत कर सकता था।
21

11

अधिकांश कंपाइलर डिबग बिल्ड में किसी भी प्रकार का अनुकूलन नहीं करते हैं।

यदि वीसी का उपयोग कर रहे हैं, तो पीडीबी जानकारी के साथ एक रिलीज़ बिल्ड चालू करें - यह आपको अनुकूलित ऐप के माध्यम से ट्रेस करेगा और आपको उम्मीद है कि आपको तब देखना चाहिए। ध्यान दें, हालांकि, एक डिबगिंग और एक अनुरक्षित बिल्ड को ट्रेस करने से आप पूरे स्थान पर कूद जाएंगे, और अक्सर आप सीधे चर का निरीक्षण नहीं कर सकते हैं क्योंकि वे केवल रजिस्टरों में समाप्त होते हैं या पूरी तरह से अनुकूलित होते हैं। यह एक "दिलचस्प" अनुभव है ...


2
Gcc क्यों -g -O3 का प्रयास करें और डीबग बिल्ड में opimifications प्राप्त करें। xlC का व्यवहार समान है।
g24l

जब आप "अधिकांश संकलक" कहते हैं: संकलक के किस संग्रह पर आप विचार करते हैं? जैसा कि बताया गया है कि डिबग बिल्ड के दौरान अनुकूलन करने वाले कम से कम दो कंपाइलर हैं - और जहां तक ​​मुझे पता है कि वीसी भी ऐसा करता है (सिवाय इसके कि आप संशोधित करें-और संभवत: जारी रखें)।
आसमान छू रहा है

7

जैसा कि ग्रेग का उल्लेख है, कंपाइलर इसे डिबग मोड में नहीं करेंगे। डिबग के लिए यह ठीक है कि एक बिल्ड बिल्ड की तुलना में धीमी गति से हो, लेकिन वे अधिक बार दुर्घटना नहीं होनी चाहिए: और यदि आप एक पूंछ कॉल अनुकूलन पर निर्भर करते हैं, तो वे ठीक ऐसा कर सकते हैं। इस वजह से अक्सर पूंछ कॉल को एक सामान्य लूप के रूप में फिर से लिखना सबसे अच्छा होता है। :-(

हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.