C 11 для оптимизации каждого и лямбда-выражения

#c #lambda #g #c 11

#c #лямбда #g #c 11

Вопрос:

Я тестирую следующий код:

 #include <iostream>
#include <vector>
#include <algorithm>
#include <ctime>
int main(int argc, char* argv[])
{
   std::vector<int> v(10000000);
   clock_t then = clock();
   if(argc <= 1)
      std::for_each(v.begin(), v.end(), [](intamp; it){ it = 10098; });
   else
      for(auto it = v.begin(); it != v.end();   it) *it = 98775;
   std::cout << clock() - then << "n";
   return 0;
}
  

Я компилирую его с помощью g 4.6, без каких-либо флагов оптимизации, и вот что я получаю:

 [javadyan@myhost experiments]$ ./a.out 
260000
[javadyan@myhost experiments]$ ./a.out aaa
330000
[javadyan@myhost experiments]$ 
  

Использование оптимизации -O1 дает следующие (неудивительные) результаты:

 [javadyan@myhost experiments]$ ./a.out 
20000
[javadyan@myhost experiments]$ ./a.out aaa
20000
  

Я использую Linux 3.0 на двухъядерном ноутбуке с частотой 2 ГГц, если это имеет значение.

Мне интересно, как в программе, скомпилированной без каких-либо оптимизаций, вызов for_each с помощью лямбда-функции может потреблять меньше часов, чем обычный цикл for ? Разве не должны быть даже небольшие накладные расходы при вызове анонимной функции? Есть ли какая-либо документация о том, как код, подобный этому

  std::for_each(v.begin(), v.end(), [](intamp; it){ it = 10098; });
  

обрабатывается ли g ? Как в этом случае ведут себя другие популярные компиляторы?

Обновить

Я не учел тот факт, что it во втором выражении сравнивается с v.end() на каждой итерации. С этим исправлением цикл for потребляет меньше часов, чем for_each . Тем не менее, мне все еще интересно, как компилятор оптимизирует for_each при использовании флага -O1 .

Ответ №1:

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

   for(auto it = v.begin(), end = v.end(); it != end;   it) *it = 98775;
  

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

 class __lambda_function_123
{
public:
    void operator()(intamp; it) const{ it = 10098; }
};

std::for_each(v.begin(), v.end(), __lambda_function_123());
  

что в дополнение к встраиванию приведет к тому же коду, что и цикл for (с учетом моей модификации).

Комментарии:

1. Попробовал. Дело закрыто 🙂 [javadyan@myhost experiments]$ ./a.out 260000 [javadyan@myhost experiments]$ ./a.out aaa 180000

2. @GrigoryJavadyan: Из любопытства, каковы результаты при включенной оптимизации? 🙂

3. Все еще хотелось бы услышать объяснение того, как g удается значительно сократить количество циклов для первого выражения (с использованием лямбда-выражения) при использовании -O1 . Я считаю, что анонимная функция встроена или что-то в этом роде?

4. @GrigoryJavadyan: да, я понимаю. Кстати, clock () это эпический провал, когда дело доходит до измерения производительности. Его точность в свою очередь снижается на тысячи. Попробуйте использовать высокоточные настенные часы, например clock_gettime () , с МОНОТОННЫМИ флагами..

5. @Grigory Убедитесь, что код выполняется вообще при оптимизации. Вы не используете результат. Если бы я был компилятором C , я бы просто исключил весь код (за исключением вывода некоторого произвольного времени). По этой причине ваш текущий тестовый код имеет недостатки. Измените его, чтобы иметь побочный эффект, который не позволяет компилятору удалять какой-либо код.

Ответ №2:

Интересно, что даже после выполнения оптимизации «end = v.end(), i ! = end» ваш вариант for_each amp; lambda по-прежнему выполняется быстрее в Visual Studio 2010 в режиме отладки.

Одна из оптимизаций, которая ускоряет работу в VS2010, заключается в том, что механизм мета-шаблонов может обнаруживать использование векторного итератора и переключаться на использование необработанных указателей на целые числа для начала / конца, что устраняет множество проверок проверки во время режима отладки как для operator , operator *, так и для operator != .