2010-04-15 10 views
9

Forse questa è una cosa specifica del compilatore. Se sì, che ne dici di gcc (g ++)? Se si utilizza un riferimento/alias variabile come questo:I riferimenti variabili (alias) incorrono nei costi di runtime?

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

Richiede effettivamente più cicli rispetto a quando non si utilizza il riferimento.

int x = 5; 
x += 10; 

In altre parole, il codice macchina cambia o "alias" si verifica solo a livello di compilatore?

Questa può sembrare una domanda stupida, ma sono curioso. Soprattutto nel caso in cui potrebbe essere conveniente rinominare temporaneamente alcune variabili membro solo in modo che il codice matematico sia un po 'più facile da leggere. Certo, non stiamo parlando esattamente di un collo di bottiglia qui ... ma è qualcosa che sto facendo e quindi mi chiedo solo se c'è qualche differenza "reale" ... o se è solo estetica.

risposta

9

Può essere considerato come un alias, ma non in termini di efficienza. Sotto il cofano, un riferimento è un puntatore con una sintassi più gradevole e garanzie di sicurezza più elevate. Quindi hai una penalità di durata dell'operazione di "dereferenziazione". A MENO CHE il compilatore lo ottimizzi, ma di solito non lo conterei.

In caso di domanda se il compilatore lo ottimizzerà o meno, non c'è altro modo che guardare l'assemblaggio generato.

+0

Buona risposta. Grazie – cheshirekow

+4

La risposta non è esattamente precisa. Uno degli intenti dietro i riferimenti è quello di implementare il concetto di un alias, nome alternativo per un oggetto esistente. Credo che ciò sia esplicitamente affermato in TC++ PL. Anche se questo non è sempre il caso, "alias" è ancora una descrizione accurata di ciò che i riferimenti sono in molti casi. – AnT

+0

@AndreyT, non ho mai sentito dell'idea che i riferimenti siano alias, puoi dirmi il paragrafo dello standard in cui ciò è implicito? –

4

L'unico modo per sapere con certezza è compilarlo ed esaminare l'output del compilatore. In generale, l'overhead per un riferimento è simile al sovraccarico per un puntatore, poiché i puntatori sono solitamente il modo in cui i riferimenti vengono implementati. Tuttavia, dato il semplice caso che stai mostrando, credo che il riferimento sarebbe stato ottimizzato.

+0

Sì, potrei un giorno fare dei semplici test e vedere se viene ottimizzato ... per ora, non è troppo importante. – cheshirekow

0

Sì, il dereferenziamento del puntatore dietro il riferimento comporta costi di runtime aggiuntivi, ma è probabilmente insignificante. Scrivi il codice in qualsiasi modo sia più chiaro da leggere e articola in modo più chiaro la semantica cui ti stai prefiggendo, quindi esegui un profiler se le prestazioni sono un problema (il collo di bottiglia è raramente quello che pensi che sia). Se sei su MacOS, lo Shark è fantastico.

+0

Fatto. Come ho detto, solo curioso. Sviluppa su Mac? ew ...;) – cheshirekow

+0

Non snobbare quello che non sai! :-) Gli strumenti di debug e di profilazione sono di prim'ordine e non costano $$$. – metasim

+0

Questa risposta è sbagliata. –

4

È vero che nella maggior parte dei casi i riferimenti implementano il concetto di "alias", un nome alternativo per l'oggetto a cui sono associati.

Tuttavia, in generale, i riferimenti vengono implementati tramite i puntatori. Tuttavia, un buon compilatore utilizzerà un puntatore effettivo per implementare il riferimento in situazioni in cui il binding effettivo viene determinato in fase di esecuzione. Se il legame è noto al momento della compilazione (e i tipi corrispondono), il compilatore normalmente implementerà il riferimento come nome alternativo per lo stesso oggetto, nel qual caso non ci sarà alcuna penalità di performance per accedere all'oggetto attraverso il riferimento (rispetto all'accesso attraverso il suo nome originale).

Il tuo esempio è uno di quelli in cui non dovresti aspettarti alcuna penalizzazione delle prestazioni dal riferimento.

4

Entrambe queste funzioni compilano esattamente lo stesso codice in g++, anche solo utilizzando -O1. (Ho aggiunto l'istruzione return per garantire che il calcolo non sia stato eliminato completamente.)

Non c'è alcun puntatore, solo un riferimento. In questo banale esempio non c'era differenza di prestazioni. Ciò non garantisce che questo sia sempre il caso (nessuna differenza di prestazioni) per tutti gli usi di riferimento, però.

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

Non sicuro che questo sia un test corretto, perché come mostrano i risultati, l'intero corpo della funzione può essere valutato in fase di compilazione. –

+1

@Daniel Pryden: Ma questo è il codice richiesto. Puoi suggerire un test più equo del codice? –

+0

Penso che il mio test sia stato un po 'più equo, perché il compilatore non ha valutato il corpo al momento della compilazione. (Ma non ho provato lo stesso codice). –

2

Ho confrontato 2 programmi su GNU/Linux. Solo l'output GCC è mostrato sotto, ma i risultati clang portano a conclusioni identiche.

versione di GCC: versione4.9.2

Clang:3.4.2

I programmi

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; 
} 

Il test

Tentativo 1: Nessun ottimizzazioni

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

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

assembly risultante di 1.cpp era più breve.

Tentativo 2: ottimizzazioni su

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

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

L'assieme risultante era completamente identici.

L'uscita assieme

1.cpp, nessuna ottimizzazione

.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, nessuna ottimizzazione

.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, con ottimizzazioni

.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.cpp, con ottimizzazioni

.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 

Conclusione

v'è alcun costo runtime quando si tratta di uscita GCC ottimizzato. Lo stesso vale per clang (testato con la versione 3.4.2): quando le ottimizzazioni sono attive, il codice assembly generato è identico in entrambi i programmi.

Problemi correlati