it-swarm.com.ru

Используя массивы или std :: vectors в C++, какова разница в производительности?

В нашем курсе C++ они предлагают больше не использовать массивы C++ в новых проектах. Насколько я знаю, сам Stroustroup предлагает не использовать массивы. Но есть ли существенные различия в производительности?

182
tunnuz

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

Использование массивов в стеке также не рекомендуется, поскольку у вас нет проверки диапазона, а передача массива потеряет любую информацию о его размере (преобразование массива в указатель). В этом случае вы должны использовать boost::array, который оборачивает массив C++ в небольшой класс и предоставляет функцию size и итераторы для его перебора.

Теперь std :: vector против собственных массивов C++ (взято из интернета):

// Comparison of Assembly code generated for basic indexing, dereferencing, 
// and increment operations on vectors and arrays/pointers.

// Assembly code was generated by gcc 4.1.0 invoked with  g++ -O3 -S  on a 
// x86_64-suse-linux machine.

#include <vector>

struct S
{
  int padding;

  std::vector<int> v;
  int * p;
  std::vector<int>::iterator i;
};

int pointer_index (S & s) { return s.p[3]; }
  // movq    32(%rdi), %rax
  // movl    12(%rax), %eax
  // ret

int vector_index (S & s) { return s.v[3]; }
  // movq    8(%rdi), %rax
  // movl    12(%rax), %eax
  // ret

// Conclusion: Indexing a vector is the same damn thing as indexing a pointer.

int pointer_deref (S & s) { return *s.p; }
  // movq    32(%rdi), %rax
  // movl    (%rax), %eax
  // ret

int iterator_deref (S & s) { return *s.i; }
  // movq    40(%rdi), %rax
  // movl    (%rax), %eax
  // ret

// Conclusion: Dereferencing a vector iterator is the same damn thing 
// as dereferencing a pointer.

void pointer_increment (S & s) { ++s.p; }
  // addq    $4, 32(%rdi)
  // ret

void iterator_increment (S & s) { ++s.i; }
  // addq    $4, 40(%rdi)
  // ret

// Conclusion: Incrementing a vector iterator is the same damn thing as 
// incrementing a pointer.

Примечание. Если вы размещаете массивы с помощью new и выделяете объекты, не относящиеся к классам (например, обычный int) или классы без определяемого пользователем конструктора и, вы не хотите, чтобы ваши элементы инициализировались изначально, используя new- выделенные массивы могут иметь преимущества в производительности, потому что std::vector инициализирует все элементы значениями по умолчанию (например, 0 для int) при создании (кредиты @bernie за то, что они меня запомнили). 

181
Johannes Schaub - litb

Преамбула для микрооптимизаторов

Помните:

«Программисты тратят огромное количество времени на размышления или беспокойство по поводу скорости некритических частей своих программ, и эти попытки повышения эффективности на самом деле оказывают сильное негативное влияние при рассмотрении вопросов отладки и обслуживания. Мы должны забыть о небольшой эффективности, скажем о 97% времени: преждевременная оптимизация - корень всего зла. Однако мы не должны упускать наши возможности в эти критические 3% ".

(Спасибо метаморфозы за полную цитату)

Не используйте массив C вместо вектора (или чего-либо еще) только потому, что вы считаете, что он быстрее, так как предполагается, что он более низкого уровня. Ты был бы неправ.

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

Тем не менее, мы можем вернуться к первоначальному вопросу.

Статический/Динамический Массив?

Классы массива C++ ведут себя лучше, чем низкоуровневый массив C, потому что они много знают о себе и могут отвечать на вопросы, а массивы C не могут. Они умеют убирать за собой. И что более важно, они обычно пишутся с использованием шаблонов и/или встраиваний, что означает, что то, что кажется для большого количества кода в отладке, разрешается в виде небольшого кода или его отсутствия вообще не производится в сборке релиза, что означает отсутствие различий с их встроенной менее безопасной конкуренцией.

В целом, это падает на две категории:

Динамические массивы

Использование указателя на массив malloc-ed/new-ed будет в лучшем случае таким же быстрым, как версия std :: vector, и намного менее безопасным (см. Сообщение litb ).

Так что используйте std :: vector.

Статические массивы

Использование статического массива будет в лучшем случае:

  • так же быстро, как std :: array version
  • и намного менее безопасно.

Так что используйте std :: array .

Неинициализированная память

Иногда использование vector вместо необработанного буфера влечет за собой видимые затраты, потому что vector инициализирует буфер при построении, в то время как код, который он заменяет, этого не сделал, как отмечено bernie by в его answer .

Если это так, то вы можете справиться с этим, используя unique_ptr вместо vector или, если случай не является исключительным в вашей кодовой строке, на самом деле написать класс buffer_owner, который будет владеть этой памятью, и дать вам простой и безопасный доступ к это, в том числе бонусы, такие как изменение размера (с помощью realloc?), или все, что вам нужно.

64
paercebal

Векторы - это массивы под капотом. Производительность такая же.

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

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

Если ваша конструкция/деструктор дорогая, вам лучше сделать вектор правильного размера для начала.

Есть простой способ продемонстрировать это. Создайте простой класс, который показывает, когда он создается/уничтожается/копируется/назначается. Создайте вектор этих вещей и начните помещать их в конец вектора. Когда вектор заполнится, произойдет каскад активности по мере изменения размера вектора. Затем попробуйте снова с вектором, размер которого соответствует ожидаемому количеству элементов. Вы увидите разницу.

29
EvilTeach

Чтобы ответить на что-то Mehrdad сказал:

Однако могут быть случаи, когда вам все еще нужны массивы. Когда взаимодействие с низкоуровневым кодом (т.е. сборкой) или старыми библиотеками, которые требуют массивов, вы не сможете использовать векторы.

Совсем не правда. Векторы хорошо разлагаются на массивы/указатели, если вы используете:

vector<double> vector;
vector.Push_back(42);

double *array = &(*vector.begin());

// pass the array to whatever low-level code you have

Это работает для всех основных реализаций STL. В следующем стандарте это будет необходимо для работы (хотя сегодня это прекрасно работает).

24
Frank Krueger

У вас еще меньше причин использовать простые массивы в C++ 11.

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

  1. Статический размер известен во время компиляции. --- std::array<T, N>
  2. Динамический с размером, известным во время выполнения и никогда не изменяемым. Типичная оптимизация здесь заключается в том, что если массив может быть размещен в стеке напрямую. -- Недоступен. Может быть dynarray в C++ TS после C++ 14. В C есть VLA 
  3. Динамический и изменяемого размера во время выполнения. --- std::vector<T>

Для 1. простых статических массивов с фиксированным числом элементов используйте std::array<T, N> в C++ 11.

Для 2. массивов фиксированного размера, указанных во время выполнения, но это не изменит их размера, есть обсуждение в C++ 14, но оно было перенесено в техническую спецификацию и наконец сделано из C++ 14.

Для 3.std::vector<T>обычно запрашивает память в куче. Это может повлиять на производительность, хотя вы можете использовать std::vector<T, MyAlloc<T>> для улучшения ситуации с пользовательским распределителем. Преимущество по сравнению с T mytype[] = new MyType[n]; заключается в том, что вы можете изменить его размер и не указывать на указатель, как это делают обычные массивы.

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

14
Germán Diago

Иди с STL. Там нет потери производительности. Алгоритмы очень эффективны и хорошо справляются с деталями, о которых большинство из нас не думает.

6
John D. Cook

О вкладе Дули

Вывод состоит в том, что массивы целых чисел быстрее, чем векторы целых чисел (в моем примере 5 раз). Однако массивы и векторы имеют одинаковую скорость для более сложных/не выровненных данных.

5
lalebarde

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

5
Mehrdad Afshari

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

В оптимизированном режиме я бы ожидал, что вектор stl приблизится к эффективности массива. Это потому, что многие из векторных методов теперь встроены.

3
Juan

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

Спецификация c ++ для конструктора std:vector, принимающего аргумент count (это третья форма), гласит:

`Создает новый контейнер из множества источников данных, по выбору, используя предоставленный пользователем распределитель alloc.

3) Создает контейнер с количеством вставленных по умолчанию экземпляров T. Копии не создаются.

Сложность

2-3) Линейный по количеству

Необработанный массив не несет затрат на инициализацию.

Смотрите также Как я могу избежать std :: vector <> для инициализации всех его элементов?

3
bernie

Разница в производительности между ними очень сильно зависит от реализации - если вы сравните плохо реализованный std :: vector с оптимальной реализацией массива, массив победит, но перевернет его, и вектор победит ...

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

Тем не менее, ИМХО, вектор выигрывает в сценарии отладки с отладочным STL, поскольку большинство реализаций STL с надлежащим режимом отладки могут по крайней мере выделить/скрыть типичные ошибки, допущенные людьми при работе со стандартными контейнерами.

Да, и не забывайте, что массив и вектор совместно используют одну и ту же структуру памяти, поэтому вы можете использовать векторы для передачи данных в устаревший код C или C++, который ожидает базовые массивы. Имейте в виду, что большинство ставок в этом сценарии отменены, и вы снова имеете дело с сырой памятью.

2
Timo Geusch

Следующий простой тест:

C++ Array vs Vector Объяснение теста производительности

противоречит выводам из «Сравнения ассемблерного кода, сгенерированного для базовых операций индексации, разыменования и приращения для векторов и массивов/указателей». 

Должна быть разница между массивами и векторами. Тест говорит так ... просто попробуйте, код есть ...

1
Hamed100101

Может быть некоторый случай Edge, когда у вас есть векторный доступ внутри встроенной функции внутри встроенной функции, когда вы вышли за пределы того, что компилятор встроит, и это вызовет вызов функции. Это было бы настолько редко, что о них не стоит беспокоиться - в общем, я бы согласился с litb .

Я удивлен, что никто еще не упомянул об этом - не беспокойтесь о производительности, пока она не окажется проблемой, а затем оцените ее.

1
Mark Ransom

Векторы используют чуть больше памяти, чем массивы, поскольку они содержат размер массива. Они также увеличивают размер жесткого диска программ и, вероятно, объем памяти программ. Эти увеличения незначительны, но могут иметь значение, если вы работаете со встроенной системой. Хотя большинство мест, где эти различия имеют значение, это места, где вы будете использовать C, а не C++.

1
Brian

Иногда массивы действительно лучше, чем векторы. Если вы всегда манипулируете Набором объектов фиксированной длины, массивы лучше. Рассмотрим следующие фрагменты кода:

int main() {
int v[3];
v[0]=1; v[1]=2;v[2]=3;
int sum;
int starttime=time(NULL);
cout << starttime << endl;
for (int i=0;i<50000;i++)
for (int j=0;j<10000;j++) {
X x(v);
sum+=x.first();
}
int endtime=time(NULL);
cout << endtime << endl;
cout << endtime - starttime << endl;

}

где векторная версия X

class X {
vector<int> vec;
public:
X(const vector<int>& v) {vec = v;}
int first() { return vec[0];}
};

и версия массива X:

class X {
int f[3];

public:
X(int a[]) {f[0]=a[0]; f[1]=a[1];f[2]=a[2];}
int first() { return f[0];}
};

Версия массива main () будет быстрее, потому что мы избегаем Издержек «new» каждый раз во внутреннем цикле.

(Этот код был размещен мной на comp.lang.c ++).

1
duli

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

1
Gabriel Isenberg

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

Векторы 2d + вызывают снижение производительности?

Суть в том, что существует небольшой объем служебных данных, когда каждый субвектор имеет информацию о размере, и не обязательно будет сериализация данных (как это происходит с многомерными массивами c). Это отсутствие сериализации может предложить больше возможностей, чем микрооптимизация. Если вы работаете с многомерными массивами, лучше всего просто расширить std :: vector и запустить собственную функцию get/set/resize bits.

1
Seph Reed

Если вам не нужно динамически регулировать размер, у вас есть накладные расходы памяти на сохранение емкости (один указатель/size_t). Вот и все.

1
Greg Rogers

Предполагая, что массив фиксированной длины (например, int* v = new int[1000]; vs std::vector<int> v(1000);, с размером v с фиксированным значением 1000), единственное соображение производительности, которое действительно имеет значение (или, по крайней мере, имело значение для меня, когда я находился в подобной дилемме), это скорость доступ к элементу. Я посмотрел векторный код STL, и вот что я нашел:

const_reference
operator[](size_type __n) const
{ return *(this->_M_impl._M_start + __n); }

Эта функция наверняка будет встроена компилятором. Таким образом, пока единственное, что вы планируете делать с v, - это обращаться к его элементам с помощью operator[], похоже, что на самом деле не должно быть никакой разницы в производительности.

0
Subh_b