C против C ++ оптимизация кода для простого создания массива и ввода / вывода - PullRequest
2 голосов
/ 18 декабря 2009

Я пытался убедить моего друга избегать использования динамически распределенных массивов и начать переходить к векторам STL. Я отправил ему пример кода, чтобы показать пару вещей, которые можно сделать с помощью STL и функторов / генераторов:

#include <iostream>
#include <vector>
#include <algorithm>
#include <iterator>

#define EVENTS 10000000

struct random_double {
  double operator() () { return (double)rand()/RAND_MAX; }
};  

int main(int argc, char **argv){

  std::vector<double> vd (EVENTS);

  generate(vd.begin(), vd.end(), random_double());
  copy(vd.begin(), vd.end(), std::ostream_iterator<double>(std::cout, "\n"));

  return 0;
} 

Его ответ на этот вопрос, хотя он и чувствует себя более элегантным, заключается в том, что его собственный код работает быстрее (почти в 2 раза!). Вот код C, на который он ответил:

#include <stdio.h>
#include <stdlib.h>
#include <malloc.h>
#include <string.h>

#define EVENTS 10000000

__inline double random_double() {
  return (double)rand()/RAND_MAX;
}


int main(int argc, char **argv){
  unsigned int i;
  double *vd;
  vd = (double *) malloc(EVENTS*sizeof(double));

  for(i=0;i<EVENTS;i++){ vd[i]=random_double(); }

  for(i=0;i<EVENTS;i++){ printf("%lf\n",vd[i]); }

  free(vd);

  return 0;
}

Итак, я запустил простой временной тест, чтобы увидеть, что происходит, и вот что я получил:

> time ./c++test > /dev/null
real    0m14.665s
user    0m14.577s
sys     0m0.092s

> time ./ctest > /dev/null
real    0m8.070s
user    0m8.001s
sys     0m0.072s

Опции компилятора, использующие g ++, были следующими: g ++ -finline -funroll-loops. Ничего особенного. Может кто-нибудь сказать мне, почему версия C ++ / STL медленнее в этом случае? Где узкое место, и смогу ли я когда-нибудь продать своего друга за использование контейнеров STL?

Ответы [ 8 ]

19 голосов
/ 18 декабря 2009

Почти наверняка использование библиотеки iostream по сравнению с printf (). Если вы хотите рассчитать время выполнения алгоритма, вы должны сделать вывод вне цикла.

17 голосов
/ 18 декабря 2009

Использование printf:

  for (std::vector<double>::iterator i = vd.begin(); i != vd.end(); ++i)
     printf("%lf\n", *i);

Результаты:

koper@elisha ~/b $ time ./cpp > /dev/null
real    0m4.985s
user    0m4.930s
sys     0m0.050s
koper@elisha ~/b $ time ./c > /dev/null
real    0m4.973s
user    0m4.920s
sys     0m0.050s

Используемые флаги: -O2 -funroll-loops -finline

4 голосов
/ 18 декабря 2009

Использование STL, особенно при использовании векторов и других полезных служебных классов, вероятно, всегда будет медленнее, чем свернутый вручную код C с использованием malloc и встроенных функций. Реального пути нет.

Как говорится, производительность - это еще не все. Использование STL дает много других преимуществ, в том числе:

  1. Лучшая ремонтопригодность: она более выразительна, поэтому вы выполняете больше в меньшем количестве кода более элегантным и чистым способом.
  2. Безопасность: использование векторов намного, намного безопаснее, чем работа с указателями и malloc напрямую.
  3. Гибкость: если использовать векторы с функторами, вам будет гораздо легче, например, если вы захотите вырастить эту коллекцию на лету.
  4. Производительность. Делая код чище, STL способствует более эффективному повторному использованию, чем множество отдельных подпрограмм на Си, для выполнения аналогичных функций.

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

2 голосов
/ 18 декабря 2009

Веря в плохую производительность итератора вставки std::cout, я попытался вставить следующий функтор:

struct Print {
  void operator()( double d ) { printf("lf\n", d); }
};

И используйте for_each для контейнера stl.

 generate(vd.begin(), vd.end(), random_double());
  //copy(vd.begin(), vd.end(), std::ostream_iterator<double>(std::cout, "\n"));
  std::for_each(vd.begin(), vd.end(), Print() );

На самом деле, теперь я получил

time.exe raw_vs_stl.exe stl > t.txt
real    0m 2.48s
user    0m 1.68s
sys     0m 0.28s

для версии STL ... в то время как «сырая» версия приводит к более или менее одинаковым.

time.exe raw_vs_stl.exe raw > t.txt
real    0m 9.22s
user    0m 7.89s
sys     0m 

0.67s Вывод: производительность вектора такая же, как и у необработанного массива. Это безопаснее и проще в использовании.

(отказ от ответственности: используется VC2005)

1 голос
/ 19 декабря 2009

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

В Visual Studio (и, как я подозреваю, во многих других идентификаторах) есть возможность просматривать ассемблер с чередованием соответствующих строк C ++. (В ВК это Debug-> Windows-> Dissassembly).

1 голос
/ 18 декабря 2009

Я бы сказал, что вы даже не используете один и тот же код.
В коде C нет проверки ошибок и происходит утечка памяти при исключении.
Чтобы сравнивать тарифы, вам нужно заставить программу на С делать то же, что и программа на С ++.

bool errorNumber = 0;   // Need a way to pass error information back from the function
int main(int argc, char **argv)
{
    ......
    {
        vd[i]=random_double();
        //
        // In C++ this logic is implicit with the use of excptions.
        // Any example where you don't do error checking is not valid.
        // In real life any code has to have this logic built in by the developer
        //
        if (errorNumber != 0)
        {    break;
        }
    }
    ........
    free(vd);  The cost of freeing the memory is not zero that needs to be factored in.
    return 0;
}
0 голосов
/ 18 декабря 2009

Могут быть случаи, когда STL работает медленнее, но ручное управление картами / наборами для множественной вставки / удаления / поиска будет трудно выполнить.
Как отметил Нил, в скорости printf выигрывает у iostream (также в статье Скотта Мейерса «Более эффективный C ++, точка 23»). Однако в более сложных системах. Это окупается, чтобы иметь возможность выписать полные классы внутри регистрации. Способ printf состоит в том, чтобы sprintf получить информацию о классе в функции и передать ее регистратору в качестве строкового параметра. Таким образом, усиление будет меньше.

0 голосов
/ 18 декабря 2009

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

Лично я использую только обработку файлов std и случайный вектор.

Но что я знаю? ;)

РЕДАКТИРОВАТЬ: Попросите вашего друга взглянуть на Тяга , которая пытается обеспечить STL-подобную функциональность для расчетов на GPU.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...