इनलाइन असेंबली टिप्पणियों को जोड़ने से GCC के उत्पन्न कोड में इस तरह के आमूल-चूल परिवर्तन क्यों होते हैं?


82

तो, मेरे पास यह कोड था:

constexpr unsigned N = 1000;
void f1(char* sum, char* a, char* b) {
    for(int i = 0; i < N; ++i) {
        sum[i] = a[i] + b[i];
    }
}

void f2(char* sum, char* a, char* b) {
    char* end = sum + N;
    while(sum != end) {
        *sum++ = *a++ + *b++;
    }
}

मैं कोड देखना चाहता था जो GCC 4.7.2 उत्पन्न करेगा। इसलिए मैं भागा g++ -march=native -O3 -masm=intel -S a.c++ -std=c++11और निम्न आउटपुट मिला:

        .file   "a.c++"
        .intel_syntax noprefix
        .text
        .p2align 4,,15
        .globl  _Z2f1PcS_S_
        .type   _Z2f1PcS_S_, @function
_Z2f1PcS_S_:
.LFB0:
        .cfi_startproc
        lea     rcx, [rdx+16]
        lea     rax, [rdi+16]
        cmp     rdi, rcx
        setae   r8b
        cmp     rdx, rax
        setae   cl
        or      cl, r8b
        je      .L5
        lea     rcx, [rsi+16]
        cmp     rdi, rcx
        setae   cl
        cmp     rsi, rax
        setae   al
        or      cl, al
        je      .L5
        xor     eax, eax
        .p2align 4,,10
        .p2align 3
.L3:
        movdqu  xmm0, XMMWORD PTR [rdx+rax]
        movdqu  xmm1, XMMWORD PTR [rsi+rax]
        paddb   xmm0, xmm1
        movdqu  XMMWORD PTR [rdi+rax], xmm0
        add     rax, 16
        cmp     rax, 992
        jne     .L3
        mov     ax, 8
        mov     r9d, 992
.L2:
        sub     eax, 1
        lea     rcx, [rdx+r9]
        add     rdi, r9
        lea     r8, [rax+1]
        add     rsi, r9
        xor     eax, eax
        .p2align 4,,10
        .p2align 3
.L4:
        movzx   edx, BYTE PTR [rcx+rax]
        add     dl, BYTE PTR [rsi+rax]
        mov     BYTE PTR [rdi+rax], dl
        add     rax, 1
        cmp     rax, r8
        jne     .L4
        rep
        ret
.L5:
        mov     eax, 1000
        xor     r9d, r9d
        jmp     .L2
        .cfi_endproc
.LFE0:
        .size   _Z2f1PcS_S_, .-_Z2f1PcS_S_
        .p2align 4,,15
        .globl  _Z2f2PcS_S_
        .type   _Z2f2PcS_S_, @function
_Z2f2PcS_S_:
.LFB1:
        .cfi_startproc
        lea     rcx, [rdx+16]
        lea     rax, [rdi+16]
        cmp     rdi, rcx
        setae   r8b
        cmp     rdx, rax
        setae   cl
        or      cl, r8b
        je      .L19
        lea     rcx, [rsi+16]
        cmp     rdi, rcx
        setae   cl
        cmp     rsi, rax
        setae   al
        or      cl, al
        je      .L19
        xor     eax, eax
        .p2align 4,,10
        .p2align 3
.L17:
        movdqu  xmm0, XMMWORD PTR [rdx+rax]
        movdqu  xmm1, XMMWORD PTR [rsi+rax]
        paddb   xmm0, xmm1
        movdqu  XMMWORD PTR [rdi+rax], xmm0
        add     rax, 16
        cmp     rax, 992
        jne     .L17
        add     rdi, 992
        add     rsi, 992
        add     rdx, 992
        mov     r8d, 8
.L16:
        xor     eax, eax
        .p2align 4,,10
        .p2align 3
.L18:
        movzx   ecx, BYTE PTR [rdx+rax]
        add     cl, BYTE PTR [rsi+rax]
        mov     BYTE PTR [rdi+rax], cl
        add     rax, 1
        cmp     rax, r8
        jne     .L18
        rep
        ret
.L19:
        mov     r8d, 1000
        jmp     .L16
        .cfi_endproc
.LFE1:
        .size   _Z2f2PcS_S_, .-_Z2f2PcS_S_
        .ident  "GCC: (GNU) 4.7.2"
        .section        .note.GNU-stack,"",@progbits

मैं असेम्बली में पढ़ता हूं, इसलिए मैंने यह जानने के लिए कुछ मार्करों को जोड़ने का फैसला किया कि छोरों के शरीर कहां गए:

constexpr unsigned N = 1000;
void f1(char* sum, char* a, char* b) {
    for(int i = 0; i < N; ++i) {
        asm("# im in ur loop");
        sum[i] = a[i] + b[i];
    }
}

void f2(char* sum, char* a, char* b) {
    char* end = sum + N;
    while(sum != end) {
        asm("# im in ur loop");
        *sum++ = *a++ + *b++;
    }
}

और जीसीसी ने इसे बाहर कर दिया:

    .file   "a.c++"
    .intel_syntax noprefix
    .text
    .p2align 4,,15
    .globl  _Z2f1PcS_S_
    .type   _Z2f1PcS_S_, @function
_Z2f1PcS_S_:
.LFB0:
    .cfi_startproc
    xor eax, eax
    .p2align 4,,10
    .p2align 3
.L2:
#APP
# 4 "a.c++" 1
    # im in ur loop
# 0 "" 2
#NO_APP
    movzx   ecx, BYTE PTR [rdx+rax]
    add cl, BYTE PTR [rsi+rax]
    mov BYTE PTR [rdi+rax], cl
    add rax, 1
    cmp rax, 1000
    jne .L2
    rep
    ret
    .cfi_endproc
.LFE0:
    .size   _Z2f1PcS_S_, .-_Z2f1PcS_S_
    .p2align 4,,15
    .globl  _Z2f2PcS_S_
    .type   _Z2f2PcS_S_, @function
_Z2f2PcS_S_:
.LFB1:
    .cfi_startproc
    xor eax, eax
    .p2align 4,,10
    .p2align 3
.L6:
#APP
# 12 "a.c++" 1
    # im in ur loop
# 0 "" 2
#NO_APP
    movzx   ecx, BYTE PTR [rdx+rax]
    add cl, BYTE PTR [rsi+rax]
    mov BYTE PTR [rdi+rax], cl
    add rax, 1
    cmp rax, 1000
    jne .L6
    rep
    ret
    .cfi_endproc
.LFE1:
    .size   _Z2f2PcS_S_, .-_Z2f2PcS_S_
    .ident  "GCC: (GNU) 4.7.2"
    .section    .note.GNU-stack,"",@progbits

यह काफी कम है, और इसमें कुछ महत्वपूर्ण अंतर हैं जैसे कि SIMD निर्देशों की कमी। मैं उसी आउटपुट की उम्मीद कर रहा था, इसके बीच में कहीं कुछ टिप्पणी। क्या मैं यहाँ कुछ गलत धारणा बना रहा हूँ? क्या जीसीसी के आशावादी को असम् टिप्पणी से बाधा है?


28
मुझे उम्मीद है कि जीसीसी (और अधिकांश संकलक) ब्लॉक बक्से की तरह ASM निर्माण का इलाज करेंगे। तो वे इस तरह के एक बॉक्स के माध्यम से क्या होता है, इसके बारे में कारण नहीं बता सकते और यह कई अनुकूलन को रोकता है, विशेष रूप से उन लोगों को लूप सीमाओं के पार ले जाता है।
इरा बैक्सटर

10
asmखाली आउटपुट और क्लोबर सूचियों के साथ विस्तारित रूप का प्रयास करें ।
केरेक एसबी

4
@ R.MartinhoFernandes: asm("# im in ur loop" : : );( प्रलेखन देखें )
माइक सेमोर

16
ध्यान दें कि -fverbose-asmध्वज को जोड़कर उत्पन्न विधानसभा को देखते समय आपको थोड़ी और मदद मिल सकती है , जो रजिस्टर के बीच चीजों को कैसे घूम रहा है, यह पहचानने में मदद करने के लिए कुछ एनोटेशन जोड़ता है।
मैथ्यू स्लिट

1
बहुत ही रोचक। लूप में अनुकूलन से बचने के लिए इस्तेमाल किया जा सकता है?
SChepurin

जवाबों:


62

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

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

विशेष रूप से, ध्यान दें:

asmकिसी भी आउटपुट ऑपरेंड के बिना एक निर्देश को पहचान के साथ एक अस्थिर asmनिर्देश के रूप में माना जाएगा ।

तथा

volatileकीवर्ड इंगित करता है कि शिक्षा महत्वपूर्ण दुष्प्रभाव है [...]

तो asmआपके लूप के अंदर की उपस्थिति ने एक वेक्टराइजेशन ऑप्टिमाइज़ेशन को रोक दिया है, क्योंकि जीसीसी ने माना है कि इसके साइड इफेक्ट्स हैं।


1
ध्यान दें कि बेसिक एसम स्टेटमेंट के साइड-इफेक्ट्स में संशोधन रजिस्टर या कोई मेमोरी शामिल नहीं होनी चाहिए जिसे आपका C ++ कोड कभी भी पढ़ता / लिखता है। लेकिन हाँ, asmकथन को C ++ एब्सट्रैक्ट मशीन में हर बार एक बार चलाना होगा, और GCC को वेक्टराइज़ नहीं करने का विकल्प चुनता है और फिर प्रति पंक्ति में 16 बार एसम का उत्सर्जन करता है paddb। मुझे लगता है कि हालांकि कानूनी होगा, क्योंकि char accesses नहीं हैं volatile। (एक "memory"लता के साथ एक विस्तारित एएसएम कथन के विपरीत )
पीटर कॉर्डेस

1
सामान्य रूप से GNU C बेसिक असमस स्टेटमेंट का उपयोग न करने के कारणों के लिए gcc.gnu.org/wiki/ConvertBasicAsmToExtended देखें । हालांकि यह उपयोग मामला (सिर्फ एक टिप्पणी मार्कर) उन कुछ में से एक है जहां इसे आज़माना अनुचित नहीं है।
पीटर कॉर्डेस 20

23

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

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


3

मैं इस बात से सहमत नहीं हूं कि "gcc यह नहीं समझता कि asm()ब्लॉक में क्या है " उदाहरण के लिए, जीसीसी अनुकूलन मापदंडों के साथ काफी अच्छी तरह से निपट सकता है, और यहां तक asm()कि ब्लॉक को फिर से व्यवस्थित करता है जैसे कि यह उत्पन्न सी कोड के साथ परस्पर क्रिया करता है। यही कारण है कि, यदि आप इनलाइन असेम्बलर को लिनक्स कर्नेल में देखते हैं, तो यह हमेशा __volatile__सुनिश्चित होता है कि कंपाइलर "कोड को इधर-उधर न करे"। मैंने अपने "rdtsc" को चारों ओर घुमा दिया है, जिससे उस समय के मेरे माप को निश्चित करने में समय लगा।

जैसा कि प्रलेखित है, gcc कुछ विशेष प्रकार के asm()ब्लॉक को "विशेष" मानता है, और इस प्रकार यह ब्लॉक के दोनों ओर कोड को ऑप्टिमाइज़ नहीं करता है।

यह कहना नहीं है कि जीसीसी, कभी-कभी, इनलाइन असेंबलर ब्लॉकों से भ्रमित नहीं होगा, या बस कुछ विशेष अनुकूलन को छोड़ने का फैसला करेगा क्योंकि यह कोडांतरक कोड के परिणामों का पालन नहीं कर सकता है, आदि, और अधिक महत्वपूर्ण बात, यह अक्सर क्लोबर टैग गायब होने से भ्रमित हो सकते हैं - इसलिए यदि आपके पास कुछ निर्देश हैं जैसेcpuidयह EAX-EDX का मान बदल देता है, लेकिन आपने कोड लिखा है ताकि यह केवल EAX का उपयोग करे, कंपाइलर EBX, ECX और EDX में चीजों को स्टोर कर सकता है, और जब ये रजिस्टर ओवरराइट हो जाते हैं, तो आपका कोड बहुत अजीब काम करता है - आप भाग्यशाली हैं, यह तुरंत दुर्घटनाग्रस्त हो जाता है - फिर यह पता लगाना आसान है कि क्या होता है। लेकिन अगर आप अशुभ हैं, तो यह लाइन के नीचे क्रैश हो जाता है ... एक और मुश्किल एक विभाजन निर्देश है जो edx में दूसरा परिणाम देता है। यदि आप मोडुलो की परवाह नहीं करते हैं, तो यह भूलना आसान है कि EDX को बदल दिया गया था।


1
gcc वास्तव में समझ में नहीं आता है कि asm ब्लॉक में क्या है - आपको इसे एक विस्तारित asm स्टेटमेंट के माध्यम से बताना होगा। इस अतिरिक्त जानकारी के बिना, gcc ऐसे ब्लॉकों के आसपास नहीं जाएगा। जीसीसी भी आपके द्वारा बताए गए मामलों में भ्रमित नहीं होता है - आपने बस यह बताकर एक प्रोग्रामिंग त्रुटि की कि यह उन रजिस्टरों का उपयोग कर सकता है जब वास्तव में, आपका कोड उन्हें देखता है।
मोनिका

देर से जवाब, लेकिन मुझे लगता है कि यह कहने लायक है। volatile asmबताता है कि जीसीसी कोड का 'महत्वपूर्ण दुष्प्रभाव' हो सकता है, और यह अधिक विशेष देखभाल के साथ इससे निपटेगा। यह अभी भी मृत-कोड-अनुकूलन के भाग के रूप में हटाया जा सकता है या बाहर ले जाया जा सकता है। C कोड के साथ सहभागिता को इस तरह के (दुर्लभ) मामले को मानने और सख्त अनुक्रमिक मूल्यांकन (जैसे asm के भीतर निर्भरता पैदा करने) लगाने की आवश्यकता है।
edmz

जीएनयू सी बेसिक एएसएम (ओपी की तरह कोई ऑपरेन्ड की कमी नहीं है asm("")) का अर्थ है, बिना किसी आउटपुट ऑपरेंड के साथ एक्सटेंडेड ऐसम की तरह। GCC asm टेम्पलेट स्ट्रिंग को नहीं समझता, केवल अड़चनें; यही कारण है कि यह सही है और पूरी तरह से विवशता का उपयोग करके संकलक के लिए अपने एएसएम का वर्णन करना आवश्यक है। टेम्प्लेट स्ट्रिंग में ओपेरेटिंग ऑपरेट्स को printfएक प्रारूप स्ट्रिंग का उपयोग करने की तुलना में कोई अधिक समझ नहीं है । TL: DR: किसी भी चीज़ के लिए GNU C बेसिक asm का उपयोग न करें, शायद इस तरह के मामलों को शुद्ध टिप्पणियों के साथ उपयोग करें।
पीटर कॉर्ड्स

-2

इस उत्तर को अब संशोधित किया गया है: यह मूल रूप से इनलाइन बेसिक एसम को एक बहुत दृढ़ता से निर्दिष्ट टूल के रूप में विचार करने वाली मानसिकता के साथ लिखा गया था, लेकिन यह जीसीसी में ऐसा कुछ नहीं है। बेसिक एसम कमजोर है और इसलिए उत्तर को संपादित किया गया था।

प्रत्येक विधानसभा टिप्पणी एक विराम बिंदु के रूप में कार्य करती है।

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

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

टिप्पणी के बिना, कोई अवलोकन बिंदु मौजूद नहीं है, और लूप को एक एकल गणितीय फ़ंक्शन के रूप में संकलित किया जाता है जो एक पर्यावरण ले रहा है और एक संशोधित वातावरण का उत्पादन कर रहा है।

आप एक व्यर्थ प्रश्न का उत्तर जानना चाहते हैं: आप जानना चाहते हैं कि प्रत्येक निर्देश (या शायद ब्लॉक, या शायद निर्देश की सीमा) कैसे संकलित किया जाता है, लेकिन कोई भी पृथक निर्देश (या ब्लॉक) संकलित नहीं किया जाता है; पूरे सामान को एक पूरे के रूप में संकलित किया गया है।

एक बेहतर सवाल होगा:

नमस्ते जीसीसी। आप क्यों मानते हैं कि यह asm आउटपुट सोर्स कोड लागू कर रहा है? कृपया हर धारणा के साथ कदम दर कदम समझाएं।

लेकिन फिर आप asm आउटपुट से अधिक समय तक एक सबूत नहीं पढ़ना चाहेंगे, जिसे GCC के आंतरिक प्रतिनिधित्व के रूप में लिखा गया है।


1
ये बिंदु मौजूद होने चाहिए ताकि आप पर्यावरण (रजिस्टरों और स्मृति की स्थिति) का निरीक्षण करें। - यह अडॉप्ट किए गए कोड के लिए सही हो सकता है। सक्षम किए गए अनुकूलन के साथ, पूरे कार्य बाइनरी से गायब हो सकते हैं। हम यहां अनुकूलित कोड के बारे में बात कर रहे हैं।
बार्टेक बानचेविकेज़

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

1
हाँ, IDK क्यों किसी को कभी होगा, और सहमत हूँ कि किसी को भी कभी नहीं करना चाहिए। जैसा कि मेरी पिछली टिप्पणी में लिंक बताता है, किसी को भी कभी भी नहीं करना चाहिए, और इसे मजबूत करने के बारे में बहस हुई है (उदाहरण के लिए एक अंतर्निहित "memory"क्लोबर के साथ) मौजूदा छोटी गाड़ी कोड के लिए एक बैंड के रूप में जो निश्चित रूप से मौजूद है। यहां तक ​​कि निर्देश जैसे asm("cli")कि केवल वास्तुशिल्प राज्य के उस हिस्से को प्रभावित करते हैं जो संकलक-जनित कोड को स्पर्श नहीं करता है, आपको अभी भी इसे आदेश दिए गए wrt की आवश्यकता है। संकलक-जनरेट किए गए लोड / स्टोर (उदाहरण के लिए यदि आप एक महत्वपूर्ण अनुभाग के चारों ओर बाधित कर रहे हैं)।
पीटर कॉर्डेस

1
इसके साथ, रेड-ज़ोन को बंद करने के लिए सुरक्षित नहीं होने के बावजूद, यहां तक ​​कि asm स्टेटमेंट के अंदर रजिस्टर (पुर्ज़ / पॉप के साथ) अक्षम मैनुअल भी सुरक्षित नहीं है, जब तक कि आप add rsp, -128पहले नहीं। लेकिन ऐसा करना बिल्कुल स्पष्ट नहीं है।
पीटर कॉर्डेस

1
वर्तमान में GCC बेसिक एसम के बराबर asm("" :::)(अव्यवस्थित रूप से अस्थिर है क्योंकि इसका कोई आउटपुट नहीं है, लेकिन इनपुट या आउटपुट निर्भरता द्वारा शेष कोड से बंधा हुआ नहीं है। और कोई "memory"क्लोब नहीं है )। और निश्चित रूप से यह %operandटेम्प्लेट स्ट्रिंग पर प्रतिस्थापन नहीं करता है , इसलिए शाब्दिक रूप से बचना %नहीं है %%। इसलिए हां, सहमत, __attribute__((naked))कार्यों के बाहर बेसिक एएसएम को कम करना और वैश्विक गुंजाइश एक अच्छा विचार होगा।
पीटर कॉर्ड्स
हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.