किसी सरणी के जीसीसी एकत्रीकरण को शून्य शून्य तत्वों सहित पूरी चीज़ को पहले क्यों भरा जाता है?


21

जीसीसी केवल शेष 96 पूर्णांक के बजाय जीरो के साथ पूरे सरणी को क्यों भरता है? गैर-शून्य इनिशियलाइज़र सभी सरणी के प्रारंभ में हैं।

void *sink;
void bar() {
    int a[100]{1,2,3,4};
    sink = a;             // a escapes the function
    asm("":::"memory");   // and compiler memory barrier
    // forces the compiler to materialize a[] in memory instead of optimizing away
}

MinGW8.1 और gcc9.2 दोनों इस तरह से ( गॉडबोल्ट कंपाइलर एक्सप्लोरर ) बनाते हैं

# gcc9.2 -O3 -m32 -mno-sse
bar():
    push    edi                       # save call-preserved EDI which rep stos uses
    xor     eax, eax                  # eax=0
    mov     ecx, 100                  # repeat-count = 100
    sub     esp, 400                  # reserve 400 bytes on the stack
    mov     edi, esp                  # dst for rep stos
        mov     DWORD PTR sink, esp       # sink = a
    rep stosd                         # memset(a, 0, 400) 

    mov     DWORD PTR [esp], 1        # then store the non-zero initializers
    mov     DWORD PTR [esp+4], 2      # over the zeroed part of the array
    mov     DWORD PTR [esp+8], 3
    mov     DWORD PTR [esp+12], 4
 # memory barrier empty asm statement is here.

    add     esp, 400                  # cleanup the stack
    pop     edi                       # and restore caller's EDI
    ret

(SSE सक्षम होने के साथ यह सभी 4 इनिशियलाइज़र्स को movdqa load / store के साथ कॉपी करेगा)

जीसीसी केवल अंतिम 96 तत्वों को क्यों नहीं करता है lea edi, [esp+16]और क्यों नहीं rep stosdकरता है? यह एक चूक अनुकूलन है, या यह किसी भी तरह से इसे इस तरह से अधिक कुशल है? (क्लैंग वास्तव memsetमें इनलाइनिंग के बजाय कॉल करता है rep stos)


संपादक का ध्यान दें: प्रश्न में मूल रूप से संयुक्त राष्ट्र के अनुकूलित आउटपुट आउटपुट थे जो उसी तरह से काम करते थे, लेकिन अक्षम कोड -O0कुछ भी साबित नहीं करता है। लेकिन यह पता चला है कि यह अनुकूलन जीसीसी द्वारा भी याद किया जाता है -O3

एक इनलाइनर को aनॉन-इनलाइन फ़ंक्शन के लिए पास करना कंपाइलर को भौतिक बनाने के लिए मजबूर करने का एक और तरीका होगा a[], लेकिन 32-बिट कोड में जो एएसएम के महत्वपूर्ण अव्यवस्था की ओर जाता है। (स्टैक आर्गेज का परिणाम धक्का होता है, जो स्टोर में स्टोर हो जाता है और सरणी को टॉयलेट करने के लिए मिलाया जाता है।)

उपयोग करने volatile a[100]{1,2,3,4}से जीसीसी बन जाता है और फिर सरणी को कॉपी करता है, जो पागल है। आम तौर पर volatileयह देखने के लिए अच्छा है कि स्थानीय चर कैसे संकलित करते हैं या स्टैक पर उन्हें बिछाते हैं।


1
@Damien आपने मेरे प्रश्न को गलत समझा। मैं पूछता हूं कि उदाहरण के लिए एक [0] को दो बार मान दिया जाता है जैसे कि a[0] = 0;और फिर a[0] = 1;
लस्सी

1
मैं असेंबली पढ़ने में सक्षम नहीं हूं, लेकिन यह कहां दिखाता है कि सरणी पूरी तरह से शून्य से भर गई है?
21

3
एक और दिलचस्प तथ्य: अधिक वस्तुओं के लिए आरंभिक, दोनों gcc और क्लैंग से पूरी सरणी को कॉपी करने के लिए वापस आते हैं .rodata... मैं विश्वास नहीं कर सकता कि 400 बाइट्स की नकल करना शून्य करने और 8 आइटम सेट करने से अधिक तेज़ है।
जेस्टर

2
आपने अनुकूलन अक्षम कर दिया; अक्षम कोड आश्चर्यजनक नहीं है जब तक आप यह सत्यापित नहीं करते कि एक ही चीज़ होती है -O3(जो यह करता है)। godbolt.org/z/rh_TNF
पीटर कॉर्ड्स

12
आप और क्या जानना चाहते हैं? यह एक चूक अनुकूलन है, इसे GCC के missed-optimizationकीवर्ड के साथ बगज़िला पर रिपोर्ट करें ।
पीटर कॉर्डेस

जवाबों:


2

सिद्धांत रूप में आपका आरंभीकरण ऐसा लग सकता है:

int a[100] = {
  [3] = 1,
  [5] = 42,
  [88] = 1,
};

इसलिए यह कैश की दृष्टि से अधिक प्रभावी हो सकता है और पूरे मेमोरी ब्लॉक को पहले शून्य कर सकता है और फिर व्यक्तिगत मान सेट कर सकता है।

इसके आधार पर व्यवहार परिवर्तन हो सकते हैं:

  • लक्ष्य वास्तुकला
  • लक्ष्य ओएस
  • सरणी की लंबाई
  • आरंभीकरण अनुपात (स्पष्ट रूप से प्रारंभिक मान / लंबाई)
  • प्रारंभिक मानों की स्थिति

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

तो ऐसा लगता है कि gcc यहाँ सबसे सामान्य दृष्टिकोण कर रहा है। एक लापता अनुकूलन की तरह लग रहा है।


हां, इस कोड के लिए एक इष्टतम रणनीति शायद सब कुछ शून्य करना होगा, या हो सकता है कि सब कुछ a[6]तत्काल या गैप के एकल स्टोर से भरे शुरुआती अंतराल के साथ शुरू हो । खासतौर पर अगर x86-64 को टारगेट किया जाए तो आप कम से कम नॉन-जीरो के साथ एक साथ 2 एलिमेंट्स करने के लिए qword स्टोर्स का इस्तेमाल कर सकते हैं। जैसे mov QWORD PTR [rsp+3*4], 1एक गलत संरेखित QWORD स्टोर के साथ तत्वों 3 और 4 करने के लिए।
पीटर कॉर्डेस

व्यवहार सिद्धांत रूप में लक्ष्य ओएस पर निर्भर हो सकता है, लेकिन वास्तविक जीसीसी में यह नहीं होगा, और इसका कोई कारण नहीं है। केवल लक्ष्य वास्तुकला (और इसके भीतर, -march=skylakeबनाम -march=k8बनाम विभिन्न सूक्ष्मजैविकों के लिए ट्यूनिंग विकल्प, जैसे बनाम बनाम -march=knlसभी सामान्य रूप से बहुत भिन्न होंगे, और शायद इसके लिए उपयुक्त रणनीति के संदर्भ में।)
पीटर कॉर्डेस

क्या C ++ में भी इसकी अनुमति है? मैंने सोचा कि यह केवल सी है
लस्सी

@Lassie आप c ++ में सही हैं, इसकी अनुमति नहीं है, लेकिन प्रश्न संकलक बैकएंड से अधिक संबंधित है, ताकि इससे कोई फर्क न पड़े। यह भी दिखाया कोड दोनों हो सकता है
vlad_tepesch

आप आसानी से ऐसे उदाहरणों का निर्माण कर सकते हैं जो C ++ में समान काम करते हैं, कुछ की घोषणा करके struct Bar{ int i; int a[100]; int j;} और Bar a{1,{2,3,4},4};समान रूप से gcc को शुरू करते हैं: शून्य ऑल आउट, और फिर 5 मान सेट करें
vlad_tepesch
हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.