เหตุใดการเริ่มต้นรวม GCC ของอาร์เรย์จึงเติมเต็มทั้งศูนย์ด้วยก่อนรวมถึงองค์ประกอบที่ไม่ใช่ศูนย์


21

ทำไม gcc เติมทั้งอาร์เรย์ด้วยศูนย์แทนที่จะเป็นจำนวนเต็ม 96 เท่านั้นที่เหลือ? initializers ที่ไม่ใช่ศูนย์ทั้งหมดอยู่ที่จุดเริ่มต้นของอาร์เรย์

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 สร้าง asm เช่นนี้ ( Godbolt compiler explorer )

# 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 จะเป็นการคัดลอก initializers ทั้ง 4 ตัวที่มีโหลด / เก็บ movdqa)

เหตุใด GCC จึงไม่ทำlea edi, [esp+16]และ memset (พร้อมrep stosd) เฉพาะองค์ประกอบ 96 รายการสุดท้ายอย่างที่ Clang ทำ นี่เป็นการเพิ่มประสิทธิภาพที่ไม่ได้รับหรือเป็นวิธีที่มีประสิทธิภาพมากกว่านี้หรือไม่ (เสียงดังกังวานเรียกจริง ๆmemsetแทนที่จะเป็นอินไลน์rep stos)


หมายเหตุจากบรรณาธิการ: คำถามแรกเริ่มมีการคอมไพล์เลอร์เอาท์พุทที่ไม่ได้รับการปรับให้เหมาะสมซึ่งทำงานในลักษณะเดียวกัน แต่โค้ดที่ไม่มีประสิทธิภาพที่-O0ไม่ได้พิสูจน์อะไรเลย แต่ปรากฎว่าการเพิ่มประสิทธิภาพนี้จะพลาดโดย GCC -O3แม้ที่

การส่งตัวชี้ไปaยังฟังก์ชันที่ไม่ใช่แบบอินไลน์จะเป็นอีกวิธีหนึ่งในการบังคับคอมไพเลอร์ให้เป็นจริงa[]แต่ในรหัส 32 บิตที่นำไปสู่ความยุ่งเหยิงที่สำคัญของ asm (สแต็ก args ส่งผลให้เกิดการพุชซึ่งจะผสมกับร้านค้าในสแต็คเพื่อเริ่มต้นอาร์เรย์)

การใช้volatile a[100]{1,2,3,4}ทำให้ GCC สร้างและคัดลอกอาเรย์ซึ่งไม่ได้ผล โดยปกติแล้วvolatileดีสำหรับการดูว่าคอมไพเลอร์เริ่มต้นตัวแปรท้องถิ่นหรือวางมันลงบนสแต็กได้อย่างไร


1
@ Damien คุณเข้าใจผิดคำถามของฉัน ผมถามว่าทำไมเช่น [0] มีการกำหนดค่าเป็นสองเท่าหากแล้วa[0] = 0; a[0] = 1;
สาว

1
ฉันไม่สามารถอ่านแอสเซมบลี แต่มันแสดงให้เห็นว่าอาร์เรย์ที่เต็มไปด้วยศูนย์?
smac89

3
ข้อเท็จจริงที่น่าสนใจอีกข้อหนึ่ง: สำหรับรายการเพิ่มเติมที่เริ่มต้นได้ทั้ง gcc และ clang จะกลับไปคัดลอกทั้งชุดจาก.rodata... ฉันไม่อยากจะเชื่อเลยว่าการคัดลอก 400 ไบต์นั้นเร็วกว่า zeroing และตั้งค่า 8 รายการ
ตลก

2
คุณปิดใช้งานการเพิ่มประสิทธิภาพ รหัสไม่มีประสิทธิภาพไม่น่าแปลกใจจนกว่าคุณจะตรวจสอบว่ามีสิ่งเดียวกันเกิดขึ้นที่-O3(ซึ่งเป็น) godbolt.org/z/rh_TNF
Peter Cordes

12
คุณต้องการรู้อะไรอีก มันเป็นการเพิ่มประสิทธิภาพที่ไม่ได้รับไปรายงานใน Bugzilla ของ GCC พร้อมmissed-optimizationคำสำคัญ
Peter Cordes

คำตอบ:


2

ในทางทฤษฎีการเริ่มต้นของคุณอาจมีลักษณะเช่นนั้น:

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

ดังนั้นจึงอาจมีประสิทธิภาพมากขึ้นในแง่ของแคชและ optimizablity แรกศูนย์บล็อกหน่วยความจำทั้งหมดแล้วตั้งค่าแต่ละค่า

อาจมีการเปลี่ยนแปลงพฤติกรรมขึ้นอยู่กับ:

  • สถาปัตยกรรมเป้าหมาย
  • ระบบปฏิบัติการเป้าหมาย
  • ความยาวของอาร์เรย์
  • อัตราส่วนการเริ่มต้น (ค่าเริ่มต้นอย่างชัดเจน / ความยาว)
  • ตำแหน่งของค่าเริ่มต้น

แน่นอนว่าในกรณีของคุณการเริ่มต้นจะถูกย่อขนาดที่จุดเริ่มต้นของอาร์เรย์และการเพิ่มประสิทธิภาพจะไม่สำคัญ

ดังนั้นดูเหมือนว่า gcc กำลังทำวิธีการทั่วไปมากที่สุดที่นี่ ดูเหมือนว่าการเพิ่มประสิทธิภาพที่ขาดหายไป


ใช่กลยุทธ์ที่ดีที่สุดสำหรับรหัสนี้อาจเป็นศูนย์ทุกอย่างหรืออาจเป็นเพียงแค่ทุกอย่างที่เริ่มต้นจากa[6]เป็นต้นไปด้วยช่องว่างต้นที่เต็มไปด้วยร้านค้าเดียวของทันทีหรือศูนย์ โดยเฉพาะอย่างยิ่งหากการกำหนดเป้าหมาย x86-64 ดังนั้นคุณสามารถใช้ร้านค้า qword เพื่อทำองค์ประกอบ 2 รายการพร้อมกันโดยที่ไม่ได้ศูนย์ล่าง เช่นmov QWORD PTR [rsp+3*4], 1การทำองค์ประกอบที่ 3 และ 4 ด้วยที่เก็บ qword แนวเดียว
Peter Cordes

ในทางทฤษฎีพฤติกรรมนั้นขึ้นอยู่กับระบบปฏิบัติการเป้าหมาย แต่ใน GCC จริงมันจะไม่เกิดขึ้นและไม่มีเหตุผล เฉพาะสถาปัตยกรรมเป้าหมาย (และภายในนั้นตัวเลือกการปรับแต่งสำหรับสถาปัตยกรรมขนาดเล็กที่แตกต่างกันเช่น-march=skylakevs. -march=k8และ-march=knlทั้งหมดจะแตกต่างกันมากโดยทั่วไปและอาจเป็นในแง่ของกลยุทธ์ที่เหมาะสมสำหรับเรื่องนี้)
Peter Cordes

สิ่งนี้อนุญาตให้ใช้ใน C ++ หรือไม่ ฉันคิดว่ามันเป็นเพียง C.
Lassie

@Lassie คุณถูกต้องใน c ++ นี่ไม่ได้รับอนุญาต แต่คำถามนั้นเกี่ยวข้องกับแบ็กเอนด์ของคอมไพเลอร์มากขึ้นดังนั้นมันจึงไม่สำคัญเท่าไหร่ รหัสที่แสดงอาจเป็นได้ทั้งคู่
vlad_tepesch

คุณสามารถสร้างตัวอย่างที่ทำงานใน C ++ ได้อย่างง่ายดายโดยการประกาศบางอย่างstruct Bar{ int i; int a[100]; int j;} และเริ่มต้นBar a{1,{2,3,4},4};gcc ทำสิ่งเดียวกัน: zero out แล้วตั้งค่า 5 ค่า
vlad_tepesch
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.