8

मैं पूंछ कॉल अनुकूलन सवाल से प्रेरित हो गया What Is Tail Call Optimization?अनुकूलन संकलक द्वारा

तो, मैं कैसे मैं इसे सादे सी में कर सकते हैं

तो देखने के लिए फैसला किया है, मैं 2 भाज्य कार्यक्रमों लिखा था , पहला जहां पूंछ कॉल अनुकूलन लागू किया जा सकता है। मैं इस तथ्य को तथ्य के रूप में कॉल करता हूं (एन, 1)।

unsigned long long int fact(int n, int cont) 
{ 
     if(n == 0) 
      return cont; 

     else return fact(n-1, n * cont); 
} 

दूसरा सामान्य रिकर्सन है जहां एकाधिक स्टैक फ्रेम की आवश्यकता होती है।

unsigned long long int fact(int n) 
{ 
    if(n == 0) 
     return 1; 

    else return n * fact(n-1); 
} 

यह इस -O2 साथ बाद के लिए एक 32 बिट संकलक द्वारा उत्पन्न विधानसभा है -O2

0x8048470 <fact>: push %ebp 
0x8048471 <fact+1>: mov %esp,%ebp 
0x8048473 <fact+3>: mov 0x8(%ebp),%edx 
0x8048476 <fact+6>: mov 0xc(%ebp),%eax 
0x8048479 <fact+9>: test %edx,%edx 
0x804847b <fact+11>: je  0x8048488 <fact+24> 
0x804847d <fact+13>: lea 0x0(%esi),%esi 
0x8048480 <fact+16>: imul %edx,%eax 
0x8048483 <fact+19>: sub $0x1,%edx 
0x8048486 <fact+22>: jne 0x8048480 <fact+16> 
0x8048488 <fact+24>: mov %eax,%edx 
0x804848a <fact+26>: sar $0x1f,%edx 
0x804848d <fact+29>: pop %ebp 
0x804848e <fact+30>: ret  

साथ पूर्व के लिए एक 32 बिट संकलक द्वारा उत्पन्न विधानसभा है।

0x8048470 <fact>: push %ebp 
0x8048471 <fact+1>: mov %esp,%ebp 
0x8048473 <fact+3>: push %edi 
0x8048474 <fact+4>: push %esi 
0x8048475 <fact+5>: push %ebx 
0x8048476 <fact+6>: sub $0x14,%esp 
0x8048479 <fact+9>: mov 0x8(%ebp),%eax 
0x804847c <fact+12>: movl $0x1,-0x18(%ebp) 
0x8048483 <fact+19>: movl $0x0,-0x14(%ebp) 
0x804848a <fact+26>: test %eax,%eax 
0x804848c <fact+28>: je  0x80484fc <fact+140> 
0x804848e <fact+30>: mov %eax,%ecx 
0x8048490 <fact+32>: mov %eax,%esi 
0x8048492 <fact+34>: sar $0x1f,%ecx 
0x8048495 <fact+37>: add $0xffffffff,%esi 
0x8048498 <fact+40>: mov %ecx,%edi 
0x804849a <fact+42>: mov %eax,%edx 
0x804849c <fact+44>: adc $0xffffffff,%edi 
0x804849f <fact+47>: sub $0x1,%eax 
0x80484a2 <fact+50>: mov %eax,-0x18(%ebp) 
0x80484a5 <fact+53>: movl $0x0,-0x14(%ebp) 
0x80484ac <fact+60>: sub -0x18(%ebp),%esi 
0x80484af <fact+63>: mov %edx,-0x20(%ebp) 
0x80484b2 <fact+66>: sbb -0x14(%ebp),%edi 
0x80484b5 <fact+69>: movl $0x1,-0x18(%ebp) 
0x80484bc <fact+76>: movl $0x0,-0x14(%ebp) 
0x80484c3 <fact+83>: mov %ecx,-0x1c(%ebp) 
0x80484c6 <fact+86>: xchg %ax,%ax 
0x80484c8 <fact+88>: mov -0x14(%ebp),%ecx 
0x80484cb <fact+91>: mov -0x18(%ebp),%ebx 
0x80484ce <fact+94>: imul -0x1c(%ebp),%ebx 
0x80484d2 <fact+98>: imul -0x20(%ebp),%ecx 
0x80484d6 <fact+102>: mov -0x18(%ebp),%eax 
0x80484d9 <fact+105>: mull -0x20(%ebp) 
0x80484dc <fact+108>: add %ebx,%ecx 
0x80484de <fact+110>: add %ecx,%edx 
0x80484e0 <fact+112>: addl $0xffffffff,-0x20(%ebp) 
0x80484e4 <fact+116>: adcl $0xffffffff,-0x1c(%ebp) 
0x80484e8 <fact+120>: mov -0x1c(%ebp),%ebx 
0x80484eb <fact+123>: mov %eax,-0x18(%ebp) 
0x80484ee <fact+126>: mov -0x20(%ebp),%eax 
0x80484f1 <fact+129>: mov %edx,-0x14(%ebp) 
0x80484f4 <fact+132>: xor %edi,%ebx 
0x80484f6 <fact+134>: xor %esi,%eax 
0x80484f8 <fact+136>: or  %eax,%ebx 
0x80484fa <fact+138>: jne 0x80484c8 <fact+88> 
0x80484fc <fact+140>: mov -0x18(%ebp),%eax 
0x80484ff <fact+143>: mov -0x14(%ebp),%edx 
0x8048502 <fact+146>: add $0x14,%esp 
0x8048505 <fact+149>: pop %ebx 
0x8048506 <fact+150>: pop %esi 
0x8048507 <fact+151>: pop %edi 
0x8048508 <fact+152>: pop %ebp 
0x8048509 <fact+153>: ret  

दोनों कार्यक्रमों को संकलित करना और असेंबली को देखते हुए, दोनों कार्यक्रमों में अभी भी रिकर्सिव कॉल हैं। लेकिन, जब मैं पूर्व में -ओ 2 विकल्प (ऊपर पोस्ट की गई असेंबली) के साथ संकलित करता हूं, तो मुझे कोई रिकर्सिव कॉल नहीं दिखाई देता है और इसलिए मुझे लगता है कि जीसीसी पूंछ कॉल ऑप्टिमाइज़ेशन सामान करता है।

लेकिन जब मैं बाद वाले -ओ 2 विकल्प के साथ संकलित करता हूं, तो यह रिकर्सिव कॉल भी हटा देता है और इसके बजाय पूर्व-ओ 2 के साथ होने वाली घटनाओं की तुलना में बड़ी संख्या में असेंबली निर्देश डालता है।

मैं समझना चाहता था कि बाद में कंपेलर क्या कर रहा है, और यह ओ 4 के साथ भी पूर्व में उत्पन्न असेंबली में क्यों परिवर्तित नहीं हो सका।

+1

आपको अपने प्रश्न (प्रासंगिक भागों) में जेनरेट की गई असेंबली शामिल करनी चाहिए, जो जवाब देने में मदद करेगा कि क्या हो रहा है। –

उत्तर

5

प्रोग्राम 2 long long गणना करता है, प्रोग्टल्राम 1 नहीं करता है।

+0

आप तेज़ थे। :) –

+0

यह भिन्नता बी/डब्ल्यू 2 कार्यक्रम क्यों? –

+2

@ पवन: पहला संस्करण cont को गुणा करता है, जो एक int है। दूसरा संस्करण परिणाम को गुणा करता है, जो एक लंबे समय तक int है। –

4

अंतर यह है कि पहला संस्करण गणना के लिए int चर का उपयोग करता है, जिसे अंत में unsigned long long तक बढ़ाया जाता है, जबकि बाद में unsigned long long का उपयोग करता है।

0

संकलक ने रिकर्सिव कॉल को लूप में अनुकूलित किया है। ध्यान दें कि आपके सी कोड में केवल शाखाएं हैं (यदि-फिर-अन्य), लेकिन असेंबलर की पिछड़ी शाखाएं (लूप) हैं।

यदि आप वास्तव में कार्रवाई में पूंछ-कॉल अनुकूलन देखना चाहते हैं, तो इसे एक अलग फ़ंक्शन कॉल करें। बेशक, यह रिकर्सन नहीं है, लेकिन संकलक छोटे परीक्षण-मामलों के लिए बहुत स्मार्ट है।

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