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

Понимание накладных расходов на лямбда-функции в С++ 11

Это уже коснулось Почему С++ лямбда медленнее обычной функции при вызове несколько раз? и С++ 0x Накладные расходы лямбда Но я думаю, что мой пример немного отличается от обсуждения в первом и противоречит результату последнего.

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

template <typename T>
void ProcessArguments(std::function<void(const T &)> process)
{}

template <typename T, typename HEAD, typename ... TAIL>
void ProcessArguments(std::function<void(const T &)> process, const HEAD &head, const TAIL &... tail)
{
  process(head);
  ProcessArguments(process, tail...);
}

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

int buffer[10];
int main(int argc, char **argv)
{
  int *p = buffer;

  for (unsigned long int i = 0; i < 10E6; ++i)
  {
    p = buffer;
    ProcessArguments<int>([&p](const int &v) { *p++ = v; }, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10);
  }
}

скомпилированный с g++ 4.6 и -O3, с использованием времени инструмента занимает более 6 секунд на моей машине, а

int buffer[10];
int *p = buffer;
void CopyIntoBuffer(const int &value)
{
  *p++ = value;
}

int main(int argc, char **argv)
{
  int *p = buffer;

  for (unsigned long int i = 0; i < 10E6; ++i)
  {
    p = buffer;
    ProcessArguments<int>(CopyIntoBuffer, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10);
  }

  return 0;
}

занимает около 1,4 секунды.

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

4b9b3361

Ответ 1

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

Теперь, для указателя на функцию, содержимое, ну, просто указатель на функцию. Для лямбда содержимое - это, по крайней мере, указатель на функцию + ссылку, которую вы захватили. Это в два раза больше для копирования. Кроме того, из-за копирования стирания std:: function любые данные будут, скорее всего, более медленными (не встроенными).

Здесь есть несколько вариантов, и лучше всего, вероятно, будет передавать не std:: function, а шаблон вместо этого. Преимущества в том, что ваш вызов метода с большей вероятностью будет встроен, никакое стирание типа не происходит с помощью std:: function, никакого копирования не происходит, все очень хорошо. Например:

template <typename TFunc>
void ProcessArguments(const TFunc& process)
{}

template <typename TFunc, typename HEAD, typename ... TAIL>
void ProcessArguments(const TFunc& process, const HEAD &head, const TAIL &... tail)
{
  process(head);
  ProcessArguments(process, tail...);
}

Второй вариант делает то же самое, но отправляет process по копии. Теперь копирование действительно происходит, но все еще аккуратно включено.

Не менее важно то, что тело process 'также может быть встроено, особенно для lamda. В зависимости от сложности копирования лямбда-объекта и его размера, передача копией может быть или не быть быстрее, чем передача по ссылке. Это может быть быстрее, потому что у компилятора может быть сложнее рассуждать о ссылке, чем локальная копия.

template <typename TFunc>
void ProcessArguments(TFunc process)
{}

template <typename TFunc, typename HEAD, typename ... TAIL>
void ProcessArguments(TFunc process, const HEAD &head, const TAIL &... tail)
{
  process(head);
  ProcessArguments(process, tail...);
}

Третий вариант, ну, попробуйте передать std:: function < > по ссылке. Таким образом, вы, по крайней мере, избегаете копирования, но вызовы не будут включены.

Вот некоторые первичные результаты (с использованием компилятора ideones 'С++ 11). Обратите внимание, что, как и ожидалось, встроенное тело лямбда дает вам лучшую производительность:

Original function:
0.483035s

Original lambda:
1.94531s


Function via template copy:
0.094748

### Lambda via template copy:
0.0264867s


Function via template reference:
0.0892594s

### Lambda via template reference:
0.0264201s


Function via std::function reference:
0.0891776s

Lambda via std::function reference:
0.09s