Мне недавно понадобилась лямбда, которая захватила несколько локальных переменных по ссылке, поэтому я сделал тестовый фрагмент, чтобы исследовать его эффективность, и скомпилировал его с помощью -O3
, используя clang 3.6:
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 ...
Очевидно, что лямбда нужен только адрес одной из переменных, из которой все остальные могут быть получены путем относительной адресации.
Вместо этого компилятор создал структуру в стеке, содержащую указатели на каждую локальную переменную, а затем передал адрес структуры в лямбда. Это так же, как если бы я написал:
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)++;
};
Это неэффективно по разным причинам, но наиболее тревожно, потому что это может привести к распределению кучи, если захвачено слишком много переменных.
Мои вопросы:
-
Тот факт, что и clang, и gcc делают это при
-O3
, заставляет меня подозревать, что что-то в стандарте фактически заставляет блокировки быть реализованы неэффективно. Это дело? -
Если да, то для каких рассуждений? Это не может быть для двоичной совместимости lambdas между компиляторами, потому что любой код, который знает о типе лямбда, гарантированно лежит в одной и той же единицы перевода.
-
Если нет, то почему эта оптимизация отсутствует у двух основных компиляторов?
EDIT:
Ниже приведен пример более эффективного кода, который я хотел бы видеть из компилятора. Этот код использует меньше пространства стека, теперь лямбда выполняет только одно указательное направление вместо двух, а размер лямбда не увеличивается в количестве захваченных переменных:
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 ...