ऐसी परिस्थितियों में जहां प्रदर्शन का अत्यधिक महत्व होता है, सी कंपाइलर संभवत: हैंड ट्यून असेंबली लैंग्वेज के साथ जो कर सकता है उसकी तुलना में सबसे तेज कोड का उत्पादन नहीं करता है। मैं कम से कम प्रतिरोध का रास्ता अपनाता हूं - इस तरह की छोटी दिनचर्या के लिए, मैं सिर्फ asm कोड लिखता हूं और एक अच्छा विचार है कि इसे निष्पादित करने में कितने चक्र लगेंगे। आप सी कोड के साथ फील कर सकते हैं और अच्छे आउटपुट उत्पन्न करने के लिए कंपाइलर प्राप्त कर सकते हैं, लेकिन आप इस तरह से आउटपुट को ट्यून करते हुए बहुत समय बर्बाद कर सकते हैं। पिछले कुछ वर्षों में कंपाइलर्स (विशेष रूप से Microsoft से) ने एक लंबा सफर तय किया है, लेकिन वे अभी भी आपके कानों के बीच कंपाइलर की तरह स्मार्ट नहीं हैं क्योंकि आप अपनी विशिष्ट स्थिति पर काम कर रहे हैं, न कि सिर्फ एक सामान्य स्थिति में। कंपाइलर कुछ निर्देशों (जैसे LDM) का उपयोग नहीं कर सकता है जो इसे गति दे सकते हैं, और यह ' लूप को अनियंत्रित करने के लिए पर्याप्त स्मार्ट होने की संभावना नहीं है। यहां ऐसा करने का एक तरीका है जो मेरी टिप्पणी में उल्लिखित 3 विचारों को शामिल करता है: लूप अनरोल करना, कैश प्रीफ़ेट और कई लोड (एलडीएम) अनुदेश का उपयोग करना। निर्देश चक्र गणना लगभग 3 घड़ियों प्रति सरणी तत्व के लिए निकलती है, लेकिन यह मेमोरी देरी को ध्यान में नहीं रखती है।
ऑपरेशन का सिद्धांत: एआरएम का सीपीयू डिजाइन एक घड़ी चक्र में अधिकांश निर्देशों को निष्पादित करता है, लेकिन निर्देशों को एक पाइपलाइन में निष्पादित किया जाता है। सी कंपाइलर बीच में अन्य निर्देशों को इंटरलेय करके पाइपलाइन की देरी को खत्म करने की कोशिश करेंगे। जब मूल सी कोड की तरह एक तंग लूप के साथ प्रस्तुत किया जाता है, तो संकलक को देरी को छिपाने में एक कठिन समय होगा क्योंकि स्मृति से पढ़े गए मूल्य की तुरंत तुलना की जानी चाहिए। 4 रजिस्टरों के 2 सेट के बीच वैकल्पिक रूप से मेरा कोड मेमोरी की देरी और डेटा को प्राप्त करने वाली पाइपलाइन को काफी कम करने के लिए है। सामान्य तौर पर, जब बड़े डेटा सेट के साथ काम करते हैं और आपका कोड उपलब्ध रजिस्टरों में से अधिकांश या सभी का उपयोग नहीं करता है, तो आपको अधिकतम प्रदर्शन नहीं मिल रहा है।
; r0 = count, r1 = source ptr, r2 = comparison value
stmfd sp!,{r4-r11} ; save non-volatile registers
mov r3,r0,LSR #3 ; loop count = total count / 8
pld [r1,#128]
ldmia r1!,{r4-r7} ; pre load first set
loop_top:
pld [r1,#128]
ldmia r1!,{r8-r11} ; pre load second set
cmp r4,r2 ; search for match
cmpne r5,r2 ; use conditional execution to avoid extra branch instructions
cmpne r6,r2
cmpne r7,r2
beq found_it
ldmia r1!,{r4-r7} ; use 2 sets of registers to hide load delays
cmp r8,r2
cmpne r9,r2
cmpne r10,r2
cmpne r11,r2
beq found_it
subs r3,r3,#1 ; decrement loop count
bne loop_top
mov r0,#0 ; return value = false (not found)
ldmia sp!,{r4-r11} ; restore non-volatile registers
bx lr ; return
found_it:
mov r0,#1 ; return true
ldmia sp!,{r4-r11}
bx lr
अपडेट:
टिप्पणियों में बहुत सारे संदेह हैं जो सोचते हैं कि मेरा अनुभव उपाख्यान / बेकार है और प्रमाण की आवश्यकता है। मैंने अनुकूलन ( -2 ) के साथ निम्न आउटपुट उत्पन्न करने के लिए (एंड्रॉइड एनडीके 9 सी से) जीसीसी 4.8 का उपयोग किया (सभी अनुकूलन लूप अन्रॉलिंग सहित चालू हो गए )। मैंने उपरोक्त प्रश्न में प्रस्तुत मूल C कोड संकलित किया। यहां GCC द्वारा निर्मित क्या है:
.L9: cmp r3, r0
beq .L8
.L3: ldr r2, [r3, #4]!
cmp r2, r1
bne .L9
mov r0, #1
.L2: add sp, sp, #1024
bx lr
.L8: mov r0, #0
b .L2
GCC का आउटपुट न केवल लूप को अनियंत्रित करता है, बल्कि LDR के बाद एक स्टॉल पर एक घड़ी बर्बाद करता है। इसके लिए कम से कम 8 घड़ियों की आवश्यकता होती है सरणी तत्व। यह पता करने के लिए कि लूप से बाहर निकलने के लिए पता का उपयोग करने का एक अच्छा काम करता है, लेकिन सभी जादुई चीजें संकलक करने में सक्षम हैं इस कोड में कहीं भी नहीं पाए जाते हैं। मैंने लक्ष्य प्लेटफ़ॉर्म पर कोड नहीं चलाया है (मेरा अपना नहीं है), लेकिन एआरएम कोड प्रदर्शन में अनुभवी कोई भी देख सकता है कि मेरा कोड तेज़ है।
अद्यतन 2:
मैंने Microsoft के विज़ुअल स्टूडियो 2013 SP2 को कोड के साथ बेहतर करने का मौका दिया। यह मेरी सरणी आरंभीकरण को वेक्टर करने के लिए नीयन निर्देशों का उपयोग करने में सक्षम था, लेकिन ओपी द्वारा लिखित रैखिक मान खोज जीसीसी द्वारा उत्पन्न (मैं इसे और अधिक पठनीय बनाने के लिए लेबल का नाम बदला) के समान था:
loop_top:
ldr r3,[r1],#4
cmp r3,r2
beq true_exit
subs r0,r0,#1
bne loop_top
false_exit: xxx
bx lr
true_exit: xxx
bx lr
जैसा कि मैंने कहा, मैं ओपी के सटीक हार्डवेयर का मालिक नहीं हूं, लेकिन मैं 3 अलग-अलग संस्करणों के एनवीडिया टेग्रा 3 और टेग्रा 4 पर प्रदर्शन का परीक्षण करूंगा और जल्द ही यहां परिणाम पोस्ट करूंगा।
अद्यतन 3:
मैंने अपना कोड और Microsoft का संकलन एआरएम कोड एक टेग्रा 3 और टेग्रा 4 (सरफेस आरटी, सर्फेस आरटी 2) पर चलाया। मैंने एक लूप की 1000000 पुनरावृत्तियों को चलाया जो एक मैच खोजने में विफल रहता है ताकि सब कुछ कैश में हो और मापना आसान हो।
My Code MS Code
Surface RT 297ns 562ns
Surface RT 2 172ns 296ns
दोनों ही मामलों में मेरा कोड लगभग दोगुना तेज चलता है। अधिकांश आधुनिक एआरएम सीपीयू शायद इसी तरह के परिणाम देंगे।