Ich bin gespannt warum der folgende Code:
#include <string>
int main()
{
std::string a = "ABCDEFGHIJKLMNO";
}
Beim Kompilieren mit -O3
ergibt sich folgender Code:
main: # @main
xor eax, eax
ret
(Ich verstehe vollkommen, dass das nicht verwendete nicht benötigt wird, a
so dass der Compiler es vollständig aus dem generierten Code weglassen kann.)
Allerdings folgendes Programm:
#include <string>
int main()
{
std::string a = "ABCDEFGHIJKLMNOP"; // <-- !!! One Extra P
}
Ausbeuten:
main: # @main
push rbx
sub rsp, 48
lea rbx, [rsp + 32]
mov qword ptr [rsp + 16], rbx
mov qword ptr [rsp + 8], 16
lea rdi, [rsp + 16]
lea rsi, [rsp + 8]
xor edx, edx
call std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >::_M_create(unsigned long&, unsigned long)
mov qword ptr [rsp + 16], rax
mov rcx, qword ptr [rsp + 8]
mov qword ptr [rsp + 32], rcx
movups xmm0, xmmword ptr [rip + .L.str]
movups xmmword ptr [rax], xmm0
mov qword ptr [rsp + 24], rcx
mov rax, qword ptr [rsp + 16]
mov byte ptr [rax + rcx], 0
mov rdi, qword ptr [rsp + 16]
cmp rdi, rbx
je .LBB0_3
call operator delete(void*)
.LBB0_3:
xor eax, eax
add rsp, 48
pop rbx
ret
mov rdi, rax
call _Unwind_Resume
.L.str:
.asciz "ABCDEFGHIJKLMNOP"
wenn mit dem gleichen kompiliert -O3
. Ich verstehe nicht, warum es nicht erkennt, a
dass der String noch nicht verwendet wird, unabhängig davon, dass der String ein Byte länger ist.
Diese Frage ist relevant für gcc 9.1 und clang 8.0 (online: https://gcc.godbolt.org/z/p1Z8Ns ), da andere Compiler in meiner Beobachtung entweder die nicht verwendete Variable (ellcc) entweder vollständig löschen oder Code dafür generieren, unabhängig von der Länge der Zeichenfolge.
c++
gcc
compilation
clang
compiler-optimization
Ferenc Deak
quelle
quelle
a
als flüchtig zu deklarieren, und Sie sehen, dass die beiden Zeichenfolgen unterschiedlich behandelt werden. Das längste scheint auf dem Haufen zugeordnet zu sein. gcc.godbolt.org/z/WUuJIBstring_view
stattdessen verwenden, wird immer noch eine längere Zeichenfolge entfernt optimiert: godbolt.org/z/AAViry-stdlib=libc++
für die Zusammenstellung mit Clang anzuhängenAntworten:
Dies ist auf die Optimierung kleiner Zeichenfolgen zurückzuführen. Wenn die Zeichenfolgendaten einschließlich des Nullterminators kleiner oder gleich 16 Zeichen sind, werden sie in einem Puffer gespeichert, der lokal für das
std::string
Objekt selbst ist. Andernfalls wird Speicher auf dem Heap zugewiesen und die Daten dort gespeichert.Die erste Zeichenfolge
"ABCDEFGHIJKLMNO"
plus der Nullterminator hat genau die Größe 16. Durch Hinzufügen"P"
wird der Puffer überschritten, dahernew
wird er intern aufgerufen, was unweigerlich zu einem Systemaufruf führt. Der Compiler kann etwas wegoptimieren, wenn sichergestellt werden kann, dass keine Nebenwirkungen auftreten. Ein Systemaufruf macht dies wahrscheinlich unmöglich - durch Einschränkung ermöglicht das Ändern eines Puffers lokal für das im Bau befindliche Objekt eine solche Nebenwirkungsanalyse.Das Verfolgen des lokalen Puffers in libstdc ++, Version 9.1, zeigt folgende Teile
bits/basic_string.h
:Hiermit können Sie die lokale Puffergröße
_S_local_capacity
und den lokalen Puffer selbst erkennen (_M_local_buf
). Wenn der Konstruktor denbasic_string::_M_construct
Aufruf auslöst , haben Sie inbits/basic_string.tcc
:wo der lokale Puffer mit seinem Inhalt gefüllt ist. Unmittelbar nach diesem Teil gelangen wir zu dem Zweig, in dem die lokale Kapazität erschöpft ist - neuer Speicher wird zugewiesen (durch Zuweisen in
M_create
), der lokale Puffer wird in den neuen Speicher kopiert und mit dem Rest des Initialisierungsarguments gefüllt:Nebenbei bemerkt, die Optimierung kleiner Zeichenfolgen ist ein eigenständiges Thema. Um ein Gefühl dafür zu bekommen, wie das Optimieren einzelner Bits im großen Maßstab einen Unterschied machen kann, würde ich diesen Vortrag empfehlen . Außerdem wird erwähnt, wie die
std::string
mitgcc
(libstdc ++) gelieferte Implementierung funktioniert und in der Vergangenheit geändert wurde, um sie an neuere Versionen des Standards anzupassen.quelle
new
ohne sich um die zugrunde liegende Implementierung kümmern zu müssen. In C ++ 14 ist dies ausdrücklich zulässig: siehe Abschnitt Zuordnung "delete[] new int[10];
kann optimiert werden".-stdlib=libc++
. Und ja, dies ermöglicht es clang8.0, die längere Zeichenfolge zu optimieren: gcc.godbolt.org/z/gVm_6R . Die Clang-Installation von Godbolt ähnelt einer normalen GNU / Linux-Installation, bei der standardmäßig libstdc ++ verwendet wird.Ich war überrascht, dass der Compiler ein
std::string
Konstruktor / Destruktor-Paar durchgesehen hat, bis ich Ihr zweites Beispiel gesehen habe. Es war nicht so. Was Sie hier sehen, ist die Optimierung kleiner Zeichenfolgen und entsprechende Optimierungen durch den Compiler.Kleine Zeichenfolgenoptimierungen liegen vor, wenn das
std::string
Objekt selbst groß genug ist, um den Inhalt der Zeichenfolge, eine Größe und möglicherweise ein Unterscheidungsbit aufzunehmen, das angibt, ob die Zeichenfolge im Modus für kleine oder große Zeichenfolgen ausgeführt wird. In diesem Fall treten keine dynamischen Zuordnungen auf und die Zeichenfolge wird imstd::string
Objekt selbst gespeichert .Compiler sind wirklich schlecht darin, nicht benötigte Zuordnungen und Freigaben zu beseitigen. Sie werden fast so behandelt, als hätten sie Nebenwirkungen und sind daher unmöglich zu beseitigen. Wenn Sie den Schwellenwert für die Optimierung kleiner Zeichenfolgen überschreiten, treten dynamische Zuordnungen auf, und das Ergebnis wird angezeigt.
Als Beispiel
void foo() { delete new int; }
ist das einfachste und dümmste mögliche Zuordnungs- / Freigabepaar, aber gcc gibt diese Baugruppe auch unter O3 aus
sub rsp, 8 mov edi, 4 call operator new(unsigned long) mov esi, 4 add rsp, 8 mov rdi, rax jmp operator delete(void*, unsigned long)
quelle
new
vom Benutzer "ersetzbar" ist. Es könnte also wirklich Nebenwirkungen haben, wie das Protokollieren von Zuordnungen. Dies macht es auch unmöglich, diestd::vector
Größenänderungrealloc
anstelle von new / copy / delete zu optimieren, es sei denn, der Compiler verfügtnew
über nicht ersetzte Link-Time-Kenntnisse , was wirklich sehr, sehr dumm ist. Die C ++ 14-Garantie des Standards,delete new ...
die optimiert werden kann, ist hilfreich, aber noch suchen nicht alle Compiler danach.Während die akzeptierte Antwort gültig ist, da C ++ 14 , es ist tatsächlich der Fall , dass
new
unddelete
Anrufe können wegoptimiert werden. Siehe diese arkane Formulierung auf cppreference:Auf diese Weise können Compiler Ihre lokale Datei vollständig löschen,
std::string
selbst wenn sie sehr lang ist. Tatsächlich - clang ++ mit libc ++ tut dies bereits (GodBolt), da libc ++ integrierte Funktionen verwendet__new
und__delete
bei der Implementierung vonstd::string
- ist dies "vom Compiler bereitgestellter Speicher". So erhalten wir:main(): xor eax, eax ret
mit im Grunde beliebig langen nicht verwendeten Zeichenfolgen.
GCC tut dies nicht, aber ich habe kürzlich Fehlerberichte darüber geöffnet. Links finden Sie in dieser SO-Antwort .
quelle