2010-04-15 5 views
9

Vielleicht ist dies eine Compiler-spezifische Sache. Wenn ja, wie wäre es mit gcc (g ++)? Wenn Sie einen variablen Referenz/alias wie folgt aus:Führen variable Referenzen (Alias) zu Laufzeitkosten?

int x = 5; 
int& y = x; 
y += 10; 

Ist es tatsächlich benötigen mehr Zyklen, als wenn wir nicht die Referenz.

Mit anderen Worten, ändert sich der Maschinencode, oder tritt der "Alias" nur auf der Compiler-Ebene auf?

Das mag wie eine dumme Frage scheinen, aber ich bin neugierig. Besonders in dem Fall, in dem es vielleicht bequem wäre, einige Elementvariablen vorübergehend umzubenennen, nur damit der mathematische Code etwas leichter zu lesen ist. Klar, wir reden hier nicht gerade über einen Engpass ... aber es ist etwas, was ich tue, und ich frage mich nur, ob es tatsächlich einen Unterschied gibt ... oder ob es nur Kosmetik ist.

Antwort

9

Es kann als ein Alias ​​behandelt werden, aber nicht in Bezug auf die Effizienz. Unter der Haube ist eine Referenz ein Zeiger mit einer schöneren Syntax und höheren Sicherheitsgarantien. Daher haben Sie eine "Dereferenzierungs" -Betriebslaufzeitstrafe. Es sei denn, der Compiler optimiert es, aber normalerweise würde ich nicht darauf zählen.

Bei der Frage, ob der Compiler es optimiert oder nicht, gibt es keinen anderen Weg, als die generierte Assembly zu betrachten.

+0

Gute Antwort. Danke – cheshirekow

+4

Die Antwort ist nicht genau genau. Eine der Absichten hinter Referenzen besteht darin, das Konzept eines Alias, alternativen Namens für ein existierendes Objekt, zu implementieren. Ich glaube, das ist explizit in TC++ PL angegeben. Während dies nicht immer der Fall ist, ist "Alias" immer noch eine genaue Beschreibung dessen, was Referenzen in vielen Fällen sind. – AnT

+0

@AndreyT, Ich habe noch nie von der Idee gehört, dass Referenzen Aliasnamen sind. Kannst du mir den Absatz des Standards angeben, wo dies enthalten ist? –

4

Der einzige Weg, um sicher zu wissen, ist es zu kompilieren und die Ausgabe des Compilers zu untersuchen. Im Allgemeinen ähnelt der Overhead für eine Referenz dem Overhead für einen Zeiger, da Pointer normalerweise die Implementierung von Referenzen sind. Angesichts des einfachen Falls, den Sie zeigen, glaube ich, dass die Referenz weg optimiert werden würde.

+0

Ja, vielleicht werde ich eines Tages ein paar einfache Tests machen und sehen, ob es optimiert wird ... vorerst ist es nicht so wichtig. – cheshirekow

0

Ja, Dereferenzierung des Zeigers hinter der Referenz verursacht zusätzliche Laufzeitkosten, ist aber wahrscheinlich unbedeutend. Schreiben Sie den Code auf die am klarsten zu lesende Art und artikulieren Sie am klarsten die Semantik, die Sie anstreben, und führen Sie dann einen Profiler aus, wenn Leistung ein Problem ist (der Flaschenhals ist selten das, was Sie vermuten). Wenn Sie auf MacOS sind, ist Shark fantastisch.

+0

Fertig. Wie gesagt, nur neugierig. Entwickeln auf Mac? ew ...;) – cheshirekow

+0

Stören Sie nicht, was Sie nicht wissen! :-) Die Debugging und Profiling Tools sind erstklassig und kosten nicht $$$. – metasim

+0

Diese Antwort ist falsch. –

4

Es ist wahr, dass Referenzen in den meisten Fällen das Konzept von "Alias" implementieren, einem alternativen Namen für das Objekt, an das sie gebunden sind.

Im Allgemeinen Fall Referenzen sind durch Zeiger implementiert. Trotzdem verwendet ein guter Compiler nur einen tatsächlichen Zeiger, um eine Referenz in Situationen zu implementieren, in denen die tatsächliche Bindung zur Laufzeit bestimmt wird. Wenn die Bindung zur Kompilierzeit bekannt ist (und die Typen übereinstimmen), implementiert der Compiler normalerweise die Referenz als alternativen Namen für das gleiche Objekt. In diesem Fall wird keine Leistungseinbuße für den Zugriff auf das Objekt über die Referenz (im Vergleich zum Zugriff) vorgenommen es durch seinen ursprünglichen Namen).

Ihr Beispiel ist eines davon, wenn Sie von der Referenz keine Leistungseinbußen erwarten.

4

Diese beiden Funktionen kompilieren zu genau dem gleichen Code in g++, auch nur mit -O1. (Ich fügte die return Anweisung hinzu, um sicherzustellen, dass die Berechnung nicht vollständig beseitigt wurde.)

Es gibt keinen Zeiger, nur eine Referenz. In diesem trivialen Beispiel gab es keinen Leistungsunterschied. Dies ist jedoch keine Garantie dafür, dass dies bei allen Verwendungen der Referenz immer der Fall ist (kein Leistungsunterschied).

int f() 
{ 
    int x = 5; 
    x += 10; 
    return x; 
} 

.

int f() 
{ 
    int x = 5; 
    int & y = x; 
    y += 10; 
    return y; 
} 

Assembler:

movl $15, %eax 
ret 
+1

Nicht sicher, dass dies ein fairer Test ist, denn wie Ihre Ergebnisse zeigen, kann der gesamte Körper der Funktion zur Kompilierzeit ausgewertet werden. –

+1

@Daniel Pryden: Aber das ist der Code, der gefragt wurde. Können Sie einen faireren Test des Codes vorschlagen? –

+0

Ich denke, mein Test war ein bisschen fairer, weil der Compiler den Körper zur Kompilierzeit nicht ausgewertet hat. (Aber ich habe nicht den gleichen Code getestet). –

2

Ich verglich 2 Programme auf GNU/Linux. Im Folgenden wird nur die GCC-Ausgabe gezeigt, aber die Ergebnisse führen zu identischen Ergebnissen.

GCC Version:4.9.2

Clang Version:3.4.2

Die Programme

1.cpp

#include <stdio.h> 
int main() 
{ 
    int x = 3; 
    printf("%d\n", x); 
    return 0; 
} 

2.cpp

#include <stdio.h> 
int main() 
{ 
    int x = 3; 
    int & y = x; 
    printf("%d\n", y); 
    return 0; 
} 

Der Test

Versuch 1: Keine Optimierungen

gcc -S --std=c++11 1.cpp

gcc -S --std=c++11 2.cpp

1.cpp die resultierende Anordnung kürzer war.

Versuch 2: Optimizations auf

gcc -S -O2 --std=c++11 1.cpp

gcc -S -O2 --std=c++11 2.cpp

Die resultierende Anordnung war völlig identisch.

Der Montage Ausgang

1.cpp, keine Optimierung

.file "1.cpp" 
    .section .rodata 
.LC0: 
    .string "%d\n" 
    .text 
    .globl main 
    .type main, @function 
main: 
.LFB0: 
    .cfi_startproc 
    pushq %rbp 
    .cfi_def_cfa_offset 16 
    .cfi_offset 6, -16 
    movq %rsp, %rbp 
    .cfi_def_cfa_register 6 
    subq $16, %rsp 
    movl $3, -4(%rbp) 
    movl -4(%rbp), %eax 
    movl %eax, %esi 
    movl $.LC0, %edi 
    movl $0, %eax 
    call printf 
    movl $0, %eax 
    leave 
    .cfi_def_cfa 7, 8 
    ret 
    .cfi_endproc 
.LFE0: 
    .size main, .-main 
    .ident "GCC: (Debian 4.9.2-10) 4.9.2" 
    .section .note.GNU-stack,"",@progbits 

2.cpp, keine Optimierung

.file "2.cpp" 
    .section .rodata 
.LC0: 
    .string "%d\n" 
    .text 
    .globl main 
    .type main, @function 
main: 
.LFB0: 
    .cfi_startproc 
    pushq %rbp 
    .cfi_def_cfa_offset 16 
    .cfi_offset 6, -16 
    movq %rsp, %rbp 
    .cfi_def_cfa_register 6 
    subq $16, %rsp 
    movl $3, -12(%rbp) 
    leaq -12(%rbp), %rax 
    movq %rax, -8(%rbp) 
    movq -8(%rbp), %rax 
    movl (%rax), %eax 
    movl %eax, %esi 
    movl $.LC0, %edi 
    movl $0, %eax 
    call printf 
    movl $0, %eax 
    leave 
    .cfi_def_cfa 7, 8 
    ret 
    .cfi_endproc 
.LFE0: 
    .size main, .-main 
    .ident "GCC: (Debian 4.9.2-10) 4.9.2" 
    .section .note.GNU-stack,"",@progbits 

1.cpp mit Optimierungen

.file "1.cpp" 
    .section .rodata.str1.1,"aMS",@progbits,1 
.LC0: 
    .string "%d\n" 
    .section .text.unlikely,"ax",@progbits 
.LCOLDB1: 
    .section .text.startup,"ax",@progbits 
.LHOTB1: 
    .p2align 4,,15 
    .globl main 
    .type main, @function 
main: 
.LFB12: 
    .cfi_startproc 
    subq $8, %rsp 
    .cfi_def_cfa_offset 16 
    movl $3, %esi 
    movl $.LC0, %edi 
    xorl %eax, %eax 
    call printf 
    xorl %eax, %eax 
    addq $8, %rsp 
    .cfi_def_cfa_offset 8 
    ret 
    .cfi_endproc 
.LFE12: 
    .size main, .-main 
    .section .text.unlikely 
.LCOLDE1: 
    .section .text.startup 
.LHOTE1: 
    .ident "GCC: (Debian 4.9.2-10) 4.9.2" 
    .section .note.GNU-stack,"",@progbits 

2.cav, mit Optimierungen

.file "1.cpp" 
    .section .rodata.str1.1,"aMS",@progbits,1 
.LC0: 
    .string "%d\n" 
    .section .text.unlikely,"ax",@progbits 
.LCOLDB1: 
    .section .text.startup,"ax",@progbits 
.LHOTB1: 
    .p2align 4,,15 
    .globl main 
    .type main, @function 
main: 
.LFB12: 
    .cfi_startproc 
    subq $8, %rsp 
    .cfi_def_cfa_offset 16 
    movl $3, %esi 
    movl $.LC0, %edi 
    xorl %eax, %eax 
    call printf 
    xorl %eax, %eax 
    addq $8, %rsp 
    .cfi_def_cfa_offset 8 
    ret 
    .cfi_endproc 
.LFE12: 
    .size main, .-main 
    .section .text.unlikely 
.LCOLDE1: 
    .section .text.startup 
.LHOTE1: 
    .ident "GCC: (Debian 4.9.2-10) 4.9.2" 
    .section .note.GNU-stack,"",@progbits 

Fazit

Es gibt keine Laufzeitkosten, wenn es um optimierte GCC Ausgang kommt. Dasselbe gilt für den Clam (getestet mit Version 3.4.2): Wenn Optimierungen aktiviert sind, ist der generierte Assembly-Code in beiden Programmen identisch.