Подтвердить что ты не робот

Как быстро лямбда-функции в GCC

Поиграв немного с С++ 0x Lambda Expression в g++, мне было интересно, насколько хорошо производительность будет вообще/конкретных ситуаций по сравнению с альтернативными способами без использования лямбда-функций.

Кто-нибудь знает более или менее всестороннее обсуждение эффективности лямбда-выражения или ситуаций, в которых следует избегать их, несмотря на больший комфорт при разработке?

4b9b3361

Ответ 1

Если вы считаете, что путь старой структуры для определения структуры с operator () является альтернативным способом, тогда нет никакой разницы, потому что lambdas в значительной степени эквивалентны этому. Просто синтаксически более удобно. Подождите, пока вы получите более полный ответ...

Ответ 2

При первом появлении лямбда-выражения у многих людей возникает смутное впечатление, что для создания этих функций существует какая-то маска компиляции во время выполнения. В частности, если у вас есть функция, которая возвращает вновь созданную функцию в качестве ее результата, казалось бы, что возвращаемая функция "создается" каждый раз, когда вызывается окружающая функция. Но это неправильно - выражение лямбда (и это верно на любом языке) содержит код, который может быть скомпилирован так же, как и любой другой код, и все это происходит статически, без каких-либо затрат, которые должны быть оставлены для среды выполнения.

Единственная проблема заключается в том, что происходит с закрытыми переменными, но это не исключает такой компиляции - для создания замыкания вы просто соединяете данные замыкания (эти переменные) с указателем на статически скомпилированный код. Следствием этого является то, что с точки зрения производительности не должно быть потерь производительности - закрытых переменных или нет. Даже с закрытыми переменными нет затрат - любой другой способ приблизиться к любой проблеме, с которой вы столкнулись, потребует некоторого сопоставления этих значений, поэтому стоимость распределения будет одинаковой независимо от того, как вы ее сохраняете (явно или неявно в замкнутых переменных). Если альтернативному решению не нужно упаковывать некоторые значения, тогда не было бы необходимости закрывать их с помощью закрытий. Это действительно то же самое, что и для локального распределения, необходимого для выполнения кода, что, очевидно, будет одинаковым независимо от того, происходит ли код из замыкания с его локальной областью или из какой-либо другой области, для которой потребуется такое же локальное состояние.

Опять же, это все, что хранится на любом языке с закрытием, и нет причин для того, чтобы код на С++ страдал от некоторых проблем с производительностью, когда другого языка не было. Одна странность в лямбда-выражениях С++ - это необходимость указать, какие переменные вы закрываете, тогда как на большинстве других языков вы по умолчанию закрываете все закрытые. Казалось бы, это дает С++-коду некоторое преимущество в том, что у него больше контроля над тем, как много вещей должно быть пакетом с закрытием, - но что-то, что очень легко для компилятора сделать автоматически, без явных аннотаций. Это приводит к одной из самых распространенных вещей, которые делают составители функциональных языков - "лямбда-подъем" - когда функция эффективно поднимается до верхнего уровня, избегая необходимости создавать замыкания во время выполнения, если они не нужны. Например, если вы пишете (используя некоторый псевдо-код JS):

function foo(x) {
  return function(y) { return y+3; }
}

тогда легко (как для компилятора, так и для человека) увидеть, что возвращаемая функция не зависит от x, и компилятор теперь может ее поднять, как если бы вы написали:

function generated_name1234(y) { return y+3; }
function foo(x) {
  return generated_name1234;
}

Аналогичные методы используются, когда закрываются только некоторые значения.

Но это расходится. Суть в том, что лямбда-выражения не являются чем-то, что накладывает некоторые ограничения производительности - закрытые переменные или нет.

(Что касается сравнения лямбда-выражений с использованием operator(), я не уверен, что сделают большинство компиляторов С++, но lambdas должен быть быстрее, поскольку нет никакой диспетчеризации во время выполнения, которая необходима для любого вызова метода. Даже если лямбда реализованные как анонимные классы с оператором (), вышеупомянутые методы могут применяться и в этом случае, что означает, что машинное оборудование для рассылки может быть скомпилировано, что означает, что у него также не должно быть дополнительных затрат, что делает его похожим на специальный случай, когда анонимный класс тривиален до точки эффективной компиляции.)

Ответ 3

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

даже закрытие, фиксирующее все контекстные переменные, должно быть в состоянии оптимизировать только контекстные переменные, которые фактически используются в лямбда.

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

lambda точно такая же, как регулярная функция, единственное различие полностью синтактическое; он определяется внутри других функций и может захватывать некоторые внешние вары, которые скомпилированы как дополнительный параметр контекста. параметр контекста может иметь инициализацию типа POD в точке, где определяется лямбда.

если конкретный компилятор (т.е. g++ или clang) ведет себя в противоречии с указанным выше, это предупреждающий знак плохой реализации. clang имеет возможность легко расширять оптимизацию, передаваемую дизайном, поэтому любые такие недостатки должны быть проще адресоваться в долгосрочной перспективе, по сравнению, например, g++

нижняя строка , если вы не используете переменные контекста, лямбда (должна быть) полностью неотличима от обычной свободной функции компилятору

Ответ 4

Как уже упоминалось, нет причин, по которым создаваемые компилятором замыкания, возникающие из lambdas, должны отличаться от производительности, чем рукописные. И для того, чтобы проверить это, я просто изменил лямбды, используемые в парсере, с написанными руками классами. Все они содержат всего несколько строк жесткого кода и выполняются миллионы раз, поэтому каждое изменение производительности будет сразу же заметным. Результат - точно такое же время выполнения. Нет, нет никакой разницы в производительности.

Ответ 5

Мы начинаем избегать использования lambdas в некоторых случаях (игровая среда), потому что созданное закрытие (если оно ухватило значения) имеет ассоциированное новое/удаление для хранения всех захваченных значений. Несмотря на то, что во многих средах это не проблема, мы занимаемся чисткой микросекунд одновременно, чтобы получить лучшую производительность. Lambdas (те, с закрытыми переменными) высоко оценили наше профилирование и были одними из первых.