2011-04-07 20 views
12

पर फ़ंक्शन की सरणी का प्रदर्शन कोड के एक बहुत ही महत्वपूर्ण महत्वपूर्ण भाग को लिख रहा हूं और मुझे फ़ंक्शन पॉइंटर्स की सरणी के साथ केस स्टेटमेंट (या अगर कथन) को प्रतिस्थापित करने के बारे में यह पागल विचार था।यदि कथन कथन

मुझे प्रदर्शित करने दें; यहाँ सामान्य संस्करण है:

while(statement) 
{ 
    /* 'option' changes on every iteration */ 

    switch(option) 
    { 
     case 0: /* simple task */ break; 
     case 1: /* simple task */ break; 
     case 2: /* simple task */ break; 
     case 3: /* simple task */ break; 
    } 
} 

और यहाँ "कॉलबैक फ़ंक्शन" संस्करण है:

void task0(void) { 
    /* simple task */ 
} 

void task1(void) { 
    /* simple task */ 
} 

void task2(void) { 
    /* simple task */ 
} 

void task3(void) { 
    /* simple task */ 
} 

void (*task[4]) (void); 

task[0] = task0; 
task[1] = task1; 
task[2] = task2; 
task[3] = task3; 

while(statement) 
{ 
    /* 'option' changes on every iteration */ 

    /* and now we call the function with 'case' number */ 
    (*task[option])(); 
} 

तो कौन-सा संस्करण तेजी से हो जाएगा? क्या फ़ंक्शन का ओवरहेड सामान्य स्विच (या यदि) कथन पर गति लाभ को समाप्त करता है?

बाद वाले संस्करण का आकलन इतना पठनीय नहीं है लेकिन मैं जो गति प्राप्त कर सकता हूं उसे ढूंढ रहा हूं।

जब मैं चीज़ों को स्थापित करता हूं तो मैं इसे बेंचमार्क करने वाला हूं लेकिन अगर किसी के पास पहले से कोई जवाब है, तो मैं परेशान नहीं हूं।

+4

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

+0

मुझे नहीं लगता कि यह अधिक (यदि कोई हो) अंतर होगा। उस ने कहा, क्या आपने उन्हें देखने की कोशिश की है कि कौन सा तेज़ है? – abeln

+0

जब तक आपके कार्य फ़ंक्शंस __tiny__ नहीं हैं तो कुछ निर्देशों को किसी भी तरह से नगण्य अंतर बनाना चाहिए। –

उत्तर

5

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

+0

मुझे लगता है कि मामले के साथ सबसे बड़ी समस्या है और अगर कथन यह है कि वांछित कार्रवाई सूची के अंत में सही केस खोजने में अधिक समय लगता है। – OptimizingBastard

+0

@ ऑप्टिमाइज़िंगबास्टर्ड - यह देखने के लिए http://en.wikipedia.org/wiki/Branch_table#Compiler_generated_branch_tables देखें कि संकलक हमेशा/elses का लंबा अनुक्रम उत्पन्न नहीं करता है। सबसे अच्छी सलाह दोनों विधियों का परीक्षण और मापना है। – AShelly

+3

मुझे लगता है कि स्विच-केस उपयोग का सबसे बड़ा लाभ पढ़ने के लिए आसान है। मैं अभी भी उन लोगों की संख्या पर हैरान हूं जिनके पास फ़ंक्शन पॉइंटर्स के साथ समस्याएं हैं, फ़ंक्शन पॉइंटर्स की एक कम संख्या कम है। पठनीयता हमेशा विचार किया जाना चाहिए। – Anthony

2

स्विच स्टेटमेंट को branch table में संकलित किया जाना चाहिए, जो अनिवार्य रूप से आपके कार्यों की सरणी के समान ही है, यदि आपके कंपाइलर में कम से कम बुनियादी अनुकूलन क्षमता है।

+0

ठीक है मुझे लगता है कि जीसीसी 4.x इसे संभालना चाहिए। मुझे बहुत चालाक चाल कहना चाहिए। मुझे पता चला कि ** - ओएस ** ध्वज 'स्विच' कथन के साथ सबसे तेज़ कोड उत्पन्न करता है लेकिन ** - ओ 3 ** झंडा 'if-else' कथन के साथ भी तेज कोड उत्पन्न करता है। – OptimizingBastard

2

कौन सा संस्करण तेजी से निर्भर करेगा। switch का निष्पक्ष कार्यान्वयन एक विशाल if ... else if ... else if ... निर्माण का अर्थ है जिसका अर्थ यह है कि यह निष्पादित करने के लिए औसत ओ (एन) समय लेता है जहां एन मामलों की संख्या है। आपकी कूद तालिका ओ (1) है, इसलिए वहां और भी अलग-अलग मामले हैं और बाद के मामलों का अधिक उपयोग किया जाता है, अधिक संभावना है कि कूद तालिका बेहतर हो। कुछ मामलों के लिए या स्विच के लिए जहां पहला मामला दूसरों की तुलना में अधिक बार चुना जाता है, मूर्खतापूर्ण कार्यान्वयन बेहतर होता है। मामला इस तथ्य से जटिल है कि संकलक एक जंप टेबल का उपयोग करना चुन सकता है, भले ही आपने स्विच लिखा हो, अगर ऐसा लगता है कि यह तेज़ होगा।

यह जानने का एकमात्र तरीका है कि आपको कौन सा चयन करना चाहिए, प्रदर्शन करना आपके कोड का परीक्षण करना है।

+0

मामलों की संख्या होने के साथ 'ओ (एन) 'औसत पर यह कैसे ले जाएगा। क्या यह 'ओ (एन/2)' जैसा कुछ नहीं होगा? जब तक कि आप कभी भी 'n'-th मामले का उपयोग न करें ... – conradkdotcom

+0

@conradk" big O "नोटेशन [' O (n * k) 'जहां k स्थिर है ओ (एन)] (https: //en.wikipedia.org/wiki/Big_O_notation#Multiplication_by_a_constant) – JeremyP

+0

ओह, ठीक है। धन्यवाद @ जेरेमीपी। – conradkdotcom

1

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

  1. एक कूद में बहुत कम एक फोन (जो, कॉल-clobbered रजिस्टरों सहेजना चाहिए ढेर, आदि को समायोजित) की तुलना में महंगा है।
  2. स्विच स्टेटमेंट मामलों में कोड कॉलर में रजिस्टरों में पहले से कैश किए गए अभिव्यक्ति मानों का उपयोग कर सकता है।

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

2

सबसे पहले, मैं randomly-pause इसे कुछ बार करने के लिए, इस प्रेषण में पर्याप्त समय व्यतीत करने के लिए इसे अनुकूलित करने के लिए भी परेशान करता हूं।

दूसरा, यदि ऐसा है, चूंकि प्रत्येक शाखा बहुत कम चक्र खर्च करती है, तो आप वांछित शाखा में जाने के लिए एक कूद तालिका चाहते हैं। switch कथन का कारण यह है कि संकलक को सुझाव देना है कि स्विच मान कॉम्पैक्ट होने पर यह एक उत्पन्न कर सकता है।

स्विच मूल्यों की सूची कितनी लंबी है? यदि यह छोटा है, तो सीढ़ी सीढ़ी अभी भी तेज हो सकती है, खासकर यदि आप शीर्ष पर अक्सर उपयोग किए जाने वाले कोड डालते हैं। एक सीढ़ी के लिए एक विकल्प (जिसे मैंने कभी भी किसी को भी कभी नहीं देखा है) एक आई-पेड़ है, जो कि बाइनरी पेड़ के बराबर कोड है।

शायद आप फ़ंक्शन पॉइंटर्स की एक सरणी नहीं चाहते हैं। हां, यह फ़ंक्शन पॉइंटर प्राप्त करने के लिए एक सरणी संदर्भ है, लेकिन फ़ंक्शन को कॉल करने में कई निर्देश हैं 'और ऐसा लगता है कि प्रत्येक फ़ंक्शन के अंदर छोटी राशि को पूरा किया जा सकता है।

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

0

मैं हाल ही में इस पोस्ट पर पहुंचे क्योंकि मैं वही सोच रहा था। मैं कोशिश करने के लिए समय ले गया। यह निश्चित रूप से आप जो कर रहे हैं उस पर निर्भर करता है, लेकिन मेरे वीएम के लिए यह एक सभ्य गति (15-25%) था, और मुझे कुछ कोड को सरल बनाने की इजाजत दी गई (जो संभवतः बहुत से गति से आया था)। एक उदाहरण (स्पष्टता के लिए सरलीकृत कोड) के रूप में, एक "के लिए" पाश में सक्षम आसानी से पाश के लिए एक का उपयोग कर कार्यान्वित किया गया था:

void OpFor(Frame* frame, Instruction* &code) 
{ 
    i32 start = GET_OP_A(code); 
    i32 stop_value = GET_OP_B(code); 
    i32 step = GET_OP_C(code); 
    // instruction count (ie. block size) 
    u32 i_count = GET_OP_D(code); 
    // pointer to end of block (NOP if it branches) 
    Instruction* end = code + i_count; 

    if(step > 0) 
    { 
    for(u32 i = start; i < stop_value; i += step) 
    { 
     // rewind instruction pointer 
     Instruction* cur = code; 

     // execute code inside for loop 
     while(cur != end) 
     { 
     cur->func(frame, cur); 
     ++cur; 
     } 
    } 
    } 
    else 
    // same with <= 
}