2010-09-27 8 views
6

मुझे पता है कि यह किसी भी अभिन्न प्रकार के एक चर को पार करते समय कहा जाता है जैसे कि int, double, long double, आदि। यह मूल्य से किया जाना चाहिए, लेकिन मुझे उत्सुकता है कि एक असेंबली प्वाइंट (प्रदर्शन-वार या स्पेस-वार) से, ऐसी स्थिति नहीं होगी जब एक अभिन्न प्रकार के चर को गुजरने के बाद पॉइंटर्स से बड़ा आकार बड़ा हो मेरे प्लेटफॉर्म पर 8 बाइट्स का आकार है और पॉइंटर्स की तुलना में बड़ा आकार है जिसमें 4 बाइट्स का आकार है; संदर्भ से अधिक कुशल होगा?संदर्भ द्वारा कार्यों के लिए एक अभिन्न प्रकार के चर को पारित करेगा, मूल्य से अधिक कुशल होगा?

उत्तर

3

सामान्य रूप से, यदि मशीन का शब्द आकार (और इस प्रकार आमतौर पर सूचक आकार) पूर्णांक के आकार से कम होता है, तो संदर्भ द्वारा गुजरना तेज़ होगा।

उदाहरण के लिए, 32-बिट मशीन पर, संदर्भ द्वारा टाइप uint64_t पास करके मूल्य से गुजरने से थोड़ा तेज़ होगा, क्योंकि मूल्य से गुजरने के लिए पूर्णांक की प्रतिलिपि बनाना शामिल है, जिसके लिए दो रजिस्टर लोड की आवश्यकता होती है। संदर्भ द्वारा पास करने में केवल एक रजिस्टर लोड शामिल होता है।

भले ही, अधिकांश भाग के लिए यह किसी भी ध्यान देने योग्य प्रदर्शन अंतर की संभावना नहीं है जब तक कि आप कड़े लूप में लाखों बार फ़ंक्शन को कॉल नहीं कर रहे हैं, इस स्थिति में यदि संभव हो तो फ़ंक्शन को इनलाइन किया जाना चाहिए।

+6

नहीं है संभव है (?) की संभावना है कि संदर्भ द्वारा पारित करने में बचत होगी बुलाए गए फ़ंक्शन में अप्रत्यक्ष मूल्य तक पहुंचने में अतिरिक्त कार्य से अधिक हो जाएं? मूल्य/रेफरी की अदलाबदलता यहां संदर्भ संदर्भ है। –

+1

@ स्टेव, शायद। शायद अधिक दुर्लभ मामलों में नहीं, उदाहरण के लिए, यदि आप 16-बिट मशीन पर 64-बिट पूर्णांक पास कर रहे हैं। –

+0

एएमडी 64 पर, दो पूर्णांक रजिस्टरों में '__uint128_t' पास करने से संभवतः स्थानीय को संग्रहीत करने, उस पते पर एक पॉइंटर की गणना करने, और उस कार्य में (उस फ़ंक्शन में जिसे 'जोड़ने' और 'adc'' मेमोरी ऑपरेंड)। यदि कॉलर के पास रजिस्टरों में मूल्य पहले से ही नहीं है, तो मैं पास-दर-रेफ तेजी से देख सकता हूं। (उदा। 'foo (p-> x) ')। रजिस्ट्रारों में पहले दो तर्कों के साथ 32 बिट कॉलिंग सम्मेलन में, तो शायद बाय-रेफ अच्छा है (मूल्यों के लिए दो के बजाय पॉइंटर के लिए केवल एक reg), और निश्चित रूप से बेहतर है कि मान regs में नहीं हैं (दो भार/दो धक्का ...) –

5

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

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

अधिकांश मामलों में करने के लिए सबसे समझदार बात यह है कि अपने कोड को सर्वोत्तम तरीके से संवाद करें ("मूल्य" प्रकार के लिए पास-दर-मूल्य जब तक तर्क न हो - सी # शब्दावली को अपनाना - अर्थात् एक "आउट" या "संदर्भ" पैरामीटर) और केवल स्पष्ट प्रदर्शन बाधा होने पर दक्षता के बारे में चिंता करें।

+3

इस पर निर्माण करने के लिए, विशेष रूप से x86-64 पर, पहले कुछ फ़ंक्शन तर्क रजिस्ट्रार के माध्यम से पारित किए जाते हैं, न कि स्टैक, जो पूर्णांक के लिए 64 बिट प्रदान करता है, फ्लोटिंग पॉइंट के लिए 80 बिट और 128 बिट एसएसई रजिस्ट्रार प्रदान करता है। और सामान्य रूप से, अभिन्न प्रकारों में प्रोसेसर ओप और पास पैरामीटर दोनों के लिए एक समान आर्किटेक्चरल रजिस्टर प्रतिनिधित्व होगा। – Brian

+0

@ ब्रायन: सच। +1 –

4

टेस्ट, टेस्ट, टेस्ट, डिस्सेम्बल, डिस्सेम्बल, डिस्सेम्बल।

सरल, देशी आकार का पूर्णांक।

 
unsigned int fun_one (unsigned int a) 
{ 
    return((a&7)+1); 
} 

unsigned int fun_two (unsigned int *a) 
{ 
    return((*a&7)+1); 
} 

कोई अनुकूलन नहीं है, इसके साथ कुछ करने के लिए उस पते पर मूल्य लोड करने के लिए संदर्भ द्वारा पारित होने पर आपके पास एक अतिरिक्त निर्देश है।

 
00000000 : 
    0: e52db004 push {fp}  ; (str fp, [sp, #-4]!) 
    4: e28db000 add fp, sp, #0 
    8: e24dd00c sub sp, sp, #12 
    c: e50b0008 str r0, [fp, #-8] 
    10: e51b3008 ldr r3, [fp, #-8] 
    14: e2033007 and r3, r3, #7 
    18: e2833001 add r3, r3, #1 
    1c: e1a00003 mov r0, r3 
    20: e28bd000 add sp, fp, #0 
    24: e49db004 pop {fp}  ; (ldr fp, [sp], #4) 
    28: e12fff1e bx lr 

0000002c : 
    2c: e52db004 push {fp}  ; (str fp, [sp, #-4]!) 
    30: e28db000 add fp, sp, #0 
    34: e24dd00c sub sp, sp, #12 
    38: e50b0008 str r0, [fp, #-8] 
    3c: e51b3008 ldr r3, [fp, #-8] 
    40: e5933000 ldr r3, [r3] 
    44: e2033007 and r3, r3, #7 
    48: e2833001 add r3, r3, #1 
    4c: e1a00003 mov r0, r3 
    50: e28bd000 add sp, fp, #0 
    54: e49db004 pop {fp}  ; (ldr fp, [sp], #4) 
    58: e12fff1e bx lr 

ऑप्टिमाइज़ेशन, -ओ 1 से -ओ 3 ने एक ही परिणाम दिया। और आप अभी भी मूल्य लोड करने के निर्देश को खो देते हैं।

 
00000000 : 
    0: e2000007 and r0, r0, #7 
    4: e2800001 add r0, r0, #1 
    8: e12fff1e bx lr 

0000000c : 
    c: e5900000 ldr r0, [r0] 
    10: e2000007 and r0, r0, #7 
    14: e2800001 add r0, r0, #1 
    18: e12fff1e bx lr 

और यह कि काफी किसी एक आकार बात के लिए आप में 64 बिट integeters, तब भी आप को संचालित करने के रजिस्टरों में संदर्भ से लोड हो रहा है अतिरिक्त शिक्षा और स्मृति चक्र जला। पारित कर सकते हैं की तरह जारी रखने के लिए जा रहा है । किसी भी चीज की अच्छी तरह से आप वास्तव में मूल्य से पास नहीं कर सकते हैं? लेकिन एक संरचना जो आप कर सकते हैं, और एक संरचना, संदर्भ या नहीं पहुंचने के लिए, शायद कुछ संबोधित करने की आवश्यकता होगी।

 
typedef struct 
{ 
    unsigned int a; 
    unsigned int b; 
    char c[4]; 
} ruct; 

unsigned int fun_one (ruct a) 
{ 
    return((a.c[3]&7)+1); 
} 

unsigned int fun_two (ruct *a) 
{ 
    return((a->c[3]&7)+1); 
} 

कोई अनुकूलन के साथ हम प्रत्येक टाई 12 निर्देशों के साथ शुरू करते हैं। मुझे यह तय करने के लिए और अधिक देखना होगा कि क्या कोई दूसरे की तुलना में अधिक घड़ी चक्र जलता है।

 
00000000 : 
    0: e52db004 push {fp}  ; (str fp, [sp, #-4]!) 
    4: e28db000 add fp, sp, #0 
    8: e24dd014 sub sp, sp, #20 
    c: e24b3010 sub r3, fp, #16 
    10: e8830007 stm r3, {r0, r1, r2} 
    14: e55b3005 ldrb r3, [fp, #-5] 
    18: e2033007 and r3, r3, #7 
    1c: e2833001 add r3, r3, #1 
    20: e1a00003 mov r0, r3 
    24: e28bd000 add sp, fp, #0 
    28: e49db004 pop {fp}  ; (ldr fp, [sp], #4) 
    2c: e12fff1e bx lr 

00000030 : 
    30: e52db004 push {fp}  ; (str fp, [sp, #-4]!) 
    34: e28db000 add fp, sp, #0 
    38: e24dd00c sub sp, sp, #12 
    3c: e50b0008 str r0, [fp, #-8] 
    40: e51b3008 ldr r3, [fp, #-8] 
    44: e5d3300b ldrb r3, [r3, #11] 
    48: e2033007 and r3, r3, #7 
    4c: e2833001 add r3, r3, #1 
    50: e1a00003 mov r0, r3 
    54: e28bd000 add sp, fp, #0 
    58: e49db004 pop {fp}  ; (ldr fp, [sp], #4) 
    5c: e12fff1e bx lr 

लेकिन देखो अनुकूलन के साथ क्या होता है। संरचना इस तरह के आकार था कि यह रजिस्टरों में फिट जब में पारित

 
00000000 : 
    0: e24dd010 sub sp, sp, #16 
    4: e28d3004 add r3, sp, #4 
    8: e8830007 stm r3, {r0, r1, r2} 
    c: e5dd100f ldrb r1, [sp, #15] 
    10: e2010007 and r0, r1, #7 
    14: e2800001 add r0, r0, #1 
    18: e28dd010 add sp, sp, #16 
    1c: e12fff1e bx lr 

00000020 : 
    20: e5d0100b ldrb r1, [r0, #11] 
    24: e2010007 and r0, r1, #7 
    28: e2800001 add r0, r0, #1 
    2c: e12fff1e bx lr 

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

आपको कंपाइलर और इंटरफ़ेस को जानने की आवश्यकता है, क्या यह रजिस्टरों में या हमेशा स्टैक पर तर्क देता है? यदि रजिस्टरों का उपयोग किया जाता है तो यह क्या करता है यदि आपके तर्कों को आरक्षित रजिस्टरों की तुलना में अधिक स्थान की आवश्यकता हो, तो क्या यह उन्हें भर देता है, फिर स्टैक का उपयोग करता है, क्या यह केवल स्टैक और कोई रजिस्ट्रार का उपयोग नहीं करता है? क्या यह तर्क धारण करने वाली स्मृति को पॉइंटर पास करता है, संदर्भ शैली से गुजरता है, लेकिन ऐसा है कि पास किया गया मान संरक्षित है।

आपको व्यक्तिगत कार्यों से परे देखना होगा कि समारोह में कॉल तैयार करने के लिए कितनी मेमोरी और पंजीकरण कार्य करना है। ढांचे के संदर्भ के साथ एक रजिस्टर भरने के लिए संरचना उदाहरण के संदर्भ में पास एक लोड या तत्काल होगा। संरचना के मूल्य से पास, एआरएम के मामले में संरचना के साथ तीन रजिस्टरों को लोड करने के लिए एक ही निर्देश होगा, लेकिन यह संभावित रूप से तीन घड़ी चक्र (या एम्बा/एक्सी बस के आधार पर 6 या 2) लेता है। अन्य प्रोसेसर के लिए आपको तीन पंजीकरण और प्रत्येक रजिस्टर के लिए डेटा घड़ी चक्र खर्च हो सकता है। इसलिए यदि जीसीसी ने वैल्यू स्ट्रक्चर उदाहरण द्वारा पास को अनुकूलित करने के लिए बेहतर काम किया है, तो संदर्भ द्वारा पास सिर्फ घड़ी चक्र या दो से इसे बाहर कर सकता था, लेकिन यह कॉलिंग फ़ंक्शन में कोड कैसा दिखता है, इस पर निर्भर करता है। वास्तव में यह जानने के लिए कि आपको सही समय पर कोड का परीक्षण करके परीक्षण करना होगा, और यह समझने के लिए अलग-अलग होना चाहिए कि यह तेज़ या धीमा क्यों हो जाता है जब आप इसे ट्यून करते हैं।

0

यदि आप एक मान गुजर रहे हैं जो केवल कई फ़ंक्शन कॉल गहराई से उपयोग किया जाता है, तो संदर्भ-से-कॉन्स्ट-टी द्वारा पारित करने के लिए अधिक कुशल हो सकता है)। यदि ऐसा है, हालांकि, आप समयपूर्व "अनुकूलन" के लिए कार्यान्वयन विवरण का खुलासा कर रहे हैं।

मुझे लगता है कि अधिकांश मामलों में, आप अनुकूलन संकलक अब कर सकते हैं की वजह से महत्वपूर्ण प्रदर्शन खो देंगे (क्योंकि आप एक पते-ले लिया चर है, और सूचक बच निकला है):

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

उदाहरण के लिए (मैं सूचक सिंटैक्स का उपयोग कर रहा हूँ बातें अधिक स्पष्ट बनाने के लिए, लेकिन एक ही संदर्भ के लिए सच है): च() और जी

long long x=0,y=1; 

for (int i = 0; i < 10; i++) { 
    x = f(&x); 
    g(&x); 

    y = f(&y); 
    g(&y); 
} 

सुंदर मानक है, लेकिन() हो सकता है कष्टप्रद:

long long f(long long * x) { 
    static long long * old; 
    if (old) { *old++; *x += *old; } 
    return ++*x; 
} 

long long g(long long * x) { 
    static long long * old; 
    if (old == x) { abort(); } 
    printf("%lld\n", *x); 
} 

आप long long const * का उपयोग करके समस्याओं में से कुछ ठीक कर सकते हैं (ताकि कार्यों मूल्य को संशोधित नहीं कर सकते हैं, लेकिन वे अभी भी इसे से पढ़ सकते हैं ...)।

आप एक ब्लॉक के अंदर समारोह कॉल चिपके हुए और चर की एक प्रति के लिए एक संदर्भ पारित करके इन आसपास पहुंच सकते हैं:

{ 
    long long tmp = x; 
    x = f(&tmp); 
} 
संबंधित मुद्दे