Warum füllt gcc das gesamte Array mit Nullen anstatt nur mit den verbleibenden 96 Ganzzahlen? Die Nicht-Null-Initialisierer befinden sich alle am Anfang des Arrays.
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 und gcc9.2 machen beide 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
(Wenn SSE aktiviert ist, werden alle 4 Initialisierer mit movdqa load / store kopiert.)
Warum macht GCC nicht nur die letzten 96 Elemente lea edi, [esp+16]
und memset (mit rep stosd
), wie es Clang tut? Ist dies eine verpasste Optimierung oder ist es irgendwie effizienter, dies auf diese Weise zu tun? (Clang ruft tatsächlich an, memset
anstatt zu inlinieren rep stos
)
Anmerkung des Herausgebers: Die Frage hatte ursprünglich eine nicht optimierte Compilerausgabe, die auf die gleiche Weise funktionierte, aber ineffizienter Code -O0
beweist nichts. Es stellt sich jedoch heraus, dass diese Optimierung von GCC auch bei verfehlt wird -O3
.
Das Übergeben eines Zeigers an a
eine Nicht-Inline-Funktion wäre eine weitere Möglichkeit, den Compiler zum Materialisieren zu zwingen a[]
, jedoch in 32-Bit-Code, was zu einer erheblichen Unordnung des ASM führt. (Stapelargumente führen zu Pushs, die mit Speichern im Stapel gemischt werden, um das Array zu initiieren.)
Mit using volatile a[100]{1,2,3,4}
wird GCC erstellt, um das Array zu erstellen und dann zu kopieren , was verrückt ist. Normalerweise volatile
ist es gut zu sehen, wie Compiler lokale Variablen initiieren oder auf dem Stapel auslegen.
a[0] = 0;
und danna[0] = 1;
..rodata
... zurück. Ich kann nicht glauben, dass das Kopieren von 400 Bytes schneller ist als das Nullstellen und Festlegen von 8 Elementen.-O3
(was auch passiert). godbolt.org/z/rh_TNFmissed-optimization
Schlüsselwort.Antworten:
Theoretisch könnte Ihre Initialisierung so aussehen:
Daher kann es im Sinne von Cache und Optimierbarkeit effektiver sein, zuerst den gesamten Speicherblock auf Null zu setzen und dann einzelne Werte festzulegen.
Möglicherweise ändert sich das Verhalten in Abhängigkeit von:
In Ihrem Fall wird die Initialisierung natürlich zu Beginn des Arrays komprimiert, und die Optimierung wäre trivial.
Es scheint also, dass gcc hier den allgemeinsten Ansatz verfolgt. Sieht nach einer fehlenden Optimierung aus.
quelle
a[6]
mit den frühen Lücken, die mit einzelnen Speichern von Sofort- oder Nullen gefüllt sind. Insbesondere wenn Sie auf x86-64 abzielen, können Sie mithilfe von qword-Speichern zwei Elemente gleichzeitig ausführen, wobei das untere nicht Null ist. zBmov QWORD PTR [rsp+3*4], 1
um die Elemente 3 und 4 mit einem falsch ausgerichteten qword-Speicher auszuführen.-march=skylake
vs.-march=k8
vs.-march=knl
im Allgemeinen und möglicherweise in Bezug auf die entsprechende Strategie dafür sehr unterschiedlich.)struct Bar{ int i; int a[100]; int j;}
und initializeBar a{1,{2,3,4},4};
gcc tut das Gleiche: null alle aus, und legen Sie dann die 5 Werte