Recentemente ho bisogno di un lambda che ha catturato più variabili locali di riferimento, così ho fatto un frammento test per indagare la sua efficienza, e compilato con -O3
usando clang 3.6:La forza standard C++ acquisita per riferimento delle variabili locali è inefficiente?
void do_something_with(void*);
void test()
{
int a = 0, b = 0, c = 0;
auto func = [&]() {
a++;
b++;
c++;
};
do_something_with((void*)&func);
}
movl $0x0,0x24(%rsp)
movl $0x0,0x20(%rsp)
movl $0x0,0x1c(%rsp)
lea 0x24(%rsp),%rax
mov %rax,(%rsp)
lea 0x20(%rsp),%rax
mov %rax,0x8(%rsp)
lea 0x1c(%rsp),%rax
mov %rax,0x10(%rsp)
lea (%rsp),%rdi
callq ...
Chiaramente la lambda ha solo bisogno dell'indirizzo di una delle variabili, da cui tutti gli altri potrebbero essere ottenuti mediante indirizzamento relativo.
Invece, il compilatore ha creato una struttura sullo stack contenente puntatori a ogni variabile locale e quindi ha passato l'indirizzo della struttura al lambda. E 'molto nello stesso modo come se avessi scritto:
int a = 0, b = 0, c = 0;
struct X
{
int *pa, *pb, *pc;
};
X x = {&a, &b, &c};
auto func = [p = &x]() {
(*p->pa)++;
(*p->pb)++;
(*p->pc)++;
};
Questo è inefficiente per vari motivi, ma la maggior parte preoccupante perché potrebbe portare a heap-allocazione se troppe variabili vengono catturati.
Le mie domande:
il fatto che sia clang e gcc fanno questo a
-O3
mi fa sospettare che qualcosa nella norma obbliga in realtà le chiusure da attuare in maniera inefficiente. È questo il caso?Se sì, allora per quale motivo? Non può essere per la compatibilità binaria di lambda tra i compilatori, poiché ogni codice che conosca il tipo di lambda è garantito che giace nella stessa unità di traduzione.
In caso contrario, perché manca questo ottimizzazione da due importanti compilatori?
EDIT:
Ecco un esempio di codice più efficiente che mi piacerebbe avere visto dal compilatore. Questo codice utilizza meno spazio di stack, lambda ora esegue un solo indicatore indiretto invece di due, e la dimensione del lambda non cresce nel numero di variabili catturati:
struct X
{
int a = 0, b = 0, c = 0;
} x;
auto func = [&x]() {
x.a++;
x.b++;
x.c++;
};
movl $0x0,0x8(%rsp)
movl $0x0,0xc(%rsp)
movl $0x0,0x10(%rsp)
lea 0x8(%rsp),%rax
mov %rax,(%rsp)
lea (%rsp),%rdi
callq ...
Non riesco a fornire informazioni di supporto dal bancomat standard, ma secondo http://en.cppreference.com/w/cpp/language/lambda 'Per le entità che vengono catturate per riferimento [...] non è specificato se membri di dati aggiuntivi sono dichiarati nel tipo di chiusura. Sembra che non sia proibito ottimizzarlo. –
Per quello che vale, se si scrive C++ idiomatico piuttosto che fare affidamento su cast non specificati tra i puntatori di funzione e oggetto, probabilmente andrà bene. ([Esempio] (https://goo.gl/vLIqde)) –
@KerrekSB: sapevo che qualcuno si sarebbe lamentato del cast 'void *' - Devo usare il lambda per impedire che tutto venga ottimizzato, ma usando std :: function crea molto rumore nell'output dell'assieme. 'Uintptr_t' sarebbe meglio? EDIT: nel tuo esempio, tutto è stato infatti costantemente propagato e in linea. – PBS