Скорость привязанного лямбда (через std:: function) vs operator() functor struct

auto lam = [](int a, int b, int c) { return a < b && b < c; };

struct functor {
  int a;
  int b;
  bool operator()(int n) const { return a < n && n < b; }
};

В первой версии мы

std::vector<std::function<bool (int)>> lamvals;
// get parameters and for each
lamvals.emplace_back(std::bind(lam, a, std::placeholders::_1, b));

Альтернативой является

std::vector<functor> lamvals;
// get parameters and for each
lamvals.emplace_back(functor{a, b});

В обоих случаях мы имеем простую итерацию

    return std::any_of(lamvals.cbegin(), lamvals.cend(),
            [n](const decltype(lamvals)::value_type & t){return t(n);});

Я вижу разницу в скорости 3: 1, когда связанная лямбда медленнее. Функтор работает почти так же быстро, как хранение целых пар и жесткое кодирование тестов. Очевидно, что hardcoding не так полезен для производственного кода, потому что в игре будет несколько функций, а не только между ними. Тем не менее, я могу пойти либо с множеством функторов, либо со многими лямбдами. У последнего меньше строк кода и выглядит более чистым, но я не думаю, что могу позволить себе эту разницу в скорости; этот код находится в критическом цикле.

Я ищу предложения ускорения.

Ответ 1

Разница между этими двумя случаями в корне сводится к тому, что с функтором компилятор точно знает, что будет вызываться во время компиляции, поэтому вызов функции может быть встроен. Интересно, что Лямбдас также имеет уникальный тип. Это означает, что когда вы используете лямбда, при компиляции (поскольку компилятор должен знать все типы), вызываемая функция уже известна, поэтому может произойти встраивание. С другой стороны, указатель функции имеет тип, основанный только на его сигнатуре. Подпись должна быть известна, чтобы ее можно было вызвать и вернуть из нее должным образом, но кроме этого указатель функции может указывать на что-либо во время выполнения, насколько это касается компилятора. То же самое можно сказать о std:: function.

Когда вы завертываете лямбду в std:: function, вы удаляете тип лямбды с точки зрения компилятора. Если это звучит странно/невозможно, подумайте об этом так: поскольку std:: function фиксированного типа может обернуть любую вызываемую с той же сигнатурой, компилятор не знает, что какая-то другая инструкция не придет одна и не изменится что std:: function обертывает.

Эта ссылка, http://goo.gl/60QFjH, показывает, что я имею в виду (кстати, страница godbolt очень удобна, я предлагаю познакомиться с ней). Я написал три примера, похожие на ваши. Первый использует std:: function, обертывающий лямбда, второй - функтор, третий - голый лямбда (развернутый), используя decltype. Вы можете посмотреть на сборку справа и увидеть, что оба последних двух вставляются, но не первые.

Я предполагаю, что вы можете использовать лямбда, чтобы делать то же самое. Вместо привязки вы можете просто делать привязку по значению с помощью lambdas a и b. Каждый раз, когда вы отбрасываете лямбда в вектор, соответствующим образом изменяйте a и b и вуаля.

Стилистически, хотя, я действительно сильно чувствую, что вы должны использовать структуру. Гораздо понятнее, что происходит. Сам факт, что вы, похоже, хотите захватить a и b в одном месте и протестировать c c в другом, означает, что это используется в вашем коде не только в одном месте. В обмен на аналогичные две дополнительные строки кода вы получаете что-то более читаемое, более легкое для отладки и более расширяемое.