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

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

17 ответов


использование массивов 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    , 32(%rdi)
  // ret

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

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

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


преамбула для людей микро-оптимизатора

помните:

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

(спасибо метаморфозы для полной цитаты)

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

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

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

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

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

в целом, он делится на две категории:

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

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

поэтому используйте std:: vector.

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

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

  • как std:: array версия
  • и гораздо менее безопасным.

чтобы использовать std:: array.

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

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


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

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

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

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

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


ответить на что-то Мехрдад сказал:

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

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

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

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

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

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


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

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

  1. Static с размером, известным во время компиляции. --- std::array<T, N>
  2. динамический с размером, известным во время выполнения и никогда не изменялся. Типичная оптимизация здесь заключается в том, что если массив может быть выделяется непосредственно в стеке. -- недоступен. Может быть!--1--> В C++ TS после C++14. В C есть VLAs
  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]; заключается в том, что вы можете изменить его размер и что он не будет распадаться на указатель, как простые массивы делать.

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


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


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


о вкладе дули.

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


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

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


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

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

тем не менее, IMHO вектор выигрывает в сценарии отладки с debug STL, поскольку большинство реализаций STL с правильным режимом отладки могут по крайней мере выделить / cathc типичные ошибки, сделанные людьми, когда работа со стандартными контейнерами.

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


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

на в C++ спецификаций на std:vector конструктор принимая


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


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

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


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


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


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

C++ Array vs Vector Performance Test объяснение

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

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


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

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() будет быстрее, потому что мы избегаем накладные расходы "нового" каждый раз во внутреннем цикле.

(этот код был размещен в компания.ленг.C++ by me).