Neden bir dizinin GCC toplam başlatması, sıfır olmayan öğeler de dahil olmak üzere her şeyi önce sıfırlarla dolduruyor?


21

Neden gcc tüm diziyi yalnızca kalan 96 tam sayı yerine sıfırlarla dolduruyor? Sıfır olmayan başlatıcıların tümü dizinin başlangıcındadır.

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 ve gcc9.2'nin ikisi de bu şekilde ortaya çıkıyor ( Godbolt derleyici gezgini ).

# 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 etkinken, 4 başlangıç ​​ayarının tümünü movdqa load / store ile kopyalar)

GCC neden sadece Clang gibi son 96 elementi yapmıyor lea edi, [esp+16]ve takmıyor rep stosd? Bu kaçırılmış bir optimizasyon mu, yoksa bu şekilde yapmak daha verimli mi? (Clang aslında memsetsatır içi yerine çağırır rep stos)


Editörün notu: Soru aslında aynı şekilde çalışan optimize edilmemiş derleyici çıktısına sahipti, ancak adresindeki verimsiz kod -O0hiçbir şey kanıtlamıyor. Ancak bu optimizasyonun GCC tarafından bile kaçırıldığı ortaya çıktı -O3.

Bir işaretçi geçen aolmayan bir satır içi işlevi gerçekleştirmek için derleyici zorlamak için başka bir yol olacaktır a[], fakat 32-bit kodu asm önemli yığılmayı sebep olduğu anlamına gelmektedir. (Yığın argümanları, diziyi başlatmak için yığına depolarla karışan itmelerle sonuçlanır.)

Kullanmak deli dizi volatile a[100]{1,2,3,4}oluşturmak ve daha sonra kopyalamak için GCC alır . Normalde volatilederleyicilerin yerel değişkenleri nasıl başlattıklarına veya yığına nasıl yerleştirdiklerine bakmak iyidir.


1
@Damien Sorumumu yanlış anladın. Örneğin bir [0] sanki iki kez değer atandığı neden sormak a[0] = 0;ve sonra a[0] = 1;.
Lassie

1
Derleme okumak mümkün değil, ama nerede dizi tamamen sıfırlarla dolu olduğunu gösterir?
smac89

3
Bir başka ilginç gerçek: başlatılan daha fazla öğe için, hem gcc hem de clang tüm diziyi kopyalamaya geri dönüyor .rodata... 400 bayt kopyalamanın sıfırlamaktan ve 8 öğeyi ayarlamaktan daha hızlı olduğuna inanamıyorum.
Soytarı

2
Optimizasyonu devre dışı bıraktınız; aynı şeyin gerçekleştiğini doğrulayana kadar verimsiz kod şaşırtıcı değildir -O3. godbolt.org/z/rh_TNF
Peter Cordes

12
Daha ne bilmek istiyorsun? Bu bir cevapsız optimizasyon, GCC'nin bugzilla missed-optimizationanahtar kelimesini bildirin .
Peter Cordes

Yanıtlar:


2

Teorik olarak başlatmanız şöyle görünebilir:

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

bu nedenle, önbellek ve en iyi duruma getirme anlamında, önce tüm bellek bloğunu sıfırlamak ve sonra tek tek değerleri ayarlamak daha etkili olabilir.

Aşağıdakilere bağlı olarak davranış değişiklikleri olabilir:

  • hedef mimari
  • hedef işletim sistemi
  • dizi uzunluğu
  • başlatma oranı (açıkça başlatılmış değerler / uzunluk)
  • başlangıç ​​değerlerinin pozisyonları

Tabii ki, sizin durumunuzda başlatma işlemi dizinin başlangıcında sıkıştırılır ve optimizasyon önemsiz olur.

Görünüşe göre gcc burada en genel yaklaşımı yapıyor. Eksik bir optimizasyona benziyor.


Evet, bu kod için en uygun strateji muhtemelen her şeyi sıfırlamak, ya da belki de a[6]hemen ya da sıfırlardan oluşan tekli mağazalarla dolu erken boşluklarla başlayarak her şey sıfırlamak olacaktır . Özellikle x86-64'ü hedefliyorsa, bir kerede 2 öğe yapmak için qword depolarını kullanabilirsiniz, daha düşük olanı sıfır değildir. örn mov QWORD PTR [rsp+3*4], 1. 3 ve 4 numaralı öğeleri yanlış hizalanmış bir qword deposu ile yapmak.
Peter Cordes

Davranış teoride hedef OS'ye bağlı olabilir, ancak gerçek GCC'de olmayacak ve bunun için bir nedeni yoktur. Sadece hedef mimari (ve bunun içinde, -march=skylakevs. -march=k8vs. gibi farklı mikro mimariler için ayar seçenekleri genel -march=knlolarak çok farklı olabilir ve belki de bunun için uygun strateji açısından.)
Peter Cordes

C ++ 'da buna izin veriliyor mu? Sadece C. olduğunu düşündüm
Lassie

@Lassie c ++ haklısın, buna izin verilmiyor, ancak soru daha çok derleyici arka ucuyla ilgili, bu yüzden o kadar önemli değil. Ayrıca gösterilen kod her ikisi de olabilir
vlad_tepesch

Hatta bazılarını bildirerek struct Bar{ int i; int a[100]; int j;} ve Bar a{1,{2,3,4},4};gcc'yi
başlatarak
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.