Почему лямбды могут быть лучше оптимизированы компилятором, чем обычные функции?

В своей книге The C++ Standard Library (Second Edition) Николай Йосуттис утверждает, что лямбда-код может быть лучше оптимизирован компилятором, чем обычные функции.

Кроме того, компиляторы C ++ оптимизируют лямбда-выражения лучше, чем они.
обычные функции.
(Стр. 213)

Это почему?

Я думал, что когда дело доходит до встраивания, не должно быть никакой разницы. Единственная причина, по которой я мог придумать, состоит в том, что компиляторы могут иметь лучший локальный контекст с лямбдами, и это может делать больше предположений и выполнять больше оптимизаций.

155

Решение

Причина в том, что лямбды функциональные объекты таким образом, передача их в шаблон функции создаст новую функцию специально для этого объекта. Таким образом, компилятор может тривиально включить лямбда-вызов.

С другой стороны, к функциям применяется старая оговорка: функция указатель передается в шаблон функции, и у компиляторов традиционно возникает много проблем с встраиванием вызовов через указатели функций. Oни Можно теоретически быть встроенным, но только если встроенная функция также встроена.

В качестве примера рассмотрим следующий шаблон функции:

template <typename Iter, typename F>
void map(Iter begin, Iter end, F f) {
for (; begin != end; ++begin)
*begin = f(*begin);
}

Называя это с лямбда как это:

int a[] = { 1, 2, 3, 4 };
map(begin(a), end(a), [](int n) { return n * 2; });

Результаты в этом экземпляре (создан компилятором):

template <>
void map<int*, _some_lambda_type>(int* begin, int* end, _some_lambda_type f) {
for (; begin != end; ++begin)
*begin = f.operator()(*begin);
}

… компилятор знает _some_lambda_type::operator () и может встроенные вызовы к нему тривиально. (И вызывая функцию map с любой другая лямбда создаст новый экземпляр map так как каждая лямбда имеет отдельный тип.)

Но когда вызывается с указателем на функцию, создание экземпляра выглядит следующим образом:

template <>
void map<int*, int (*)(int)>(int* begin, int* end, int (*f)(int)) {
for (; begin != end; ++begin)
*begin = f(*begin);
}

… и здесь f указывает на разные адреса для каждого звонка map и, следовательно, компилятор не может встроенные вызовы f если окружающие не призывают map также был встроен, так что компилятор может разрешить f к одной конкретной функции.

160

Другие решения

Потому что, когда вы передаете «функцию» алгоритму, вы фактически передаете указатель на функцию, поэтому он должен сделать косвенный вызов через указатель на функцию. Когда вы используете лямбду, вы передаете объект в экземпляр шаблона, специально созданный для этого типа, и вызов лямбда-функции является прямым вызовом, а не вызовом через указатель на функцию, поэтому он может быть встроен.

24

По вопросам рекламы ammmcru@yandex.ru
Adblock
detector