#c #performance #vector #c 17
#c #Производительность #вектор #c 17
Вопрос:
В попытке понять, как ведут себя векторы, я закодировал следующие три игрушечных примера:
- vector_using_assignment.cc : Инициализируйте вектор до 1 000 000 и назначьте его элементы в цикле for
// 1. vector_using_assignment
#include <iostream>
#include <vector>
int main(int argc, char *argv[]) {
int N = *argv[1];
std::vector<int> V(N);
for (int i =0; i < N; i ) {
V[i] = i;
}
}
$ g -O0 -std=c 17 vector_using_assignment.cc -o vector_using_assignment
$ time ./vector_using_assignment 1000000
real 0m0.004s
user 0m0.003s
sys 0m0.001s
- vector_using_push_back.cc : Создайте пустой вектор, а затем назначьте его элементы в цикле for, используя метод push_back
// 2. vector_using_push_back.cc
#include <iostream>
#include <vector>
int main(int argc, char *argv[]) {
int N = *argv[1];
std::vector<int> V;
for (int i =0; i < N; i ) {
V.push_back(i);
}
}
$ g -O0 -std=c 17 vector_using_push_back.cc -o vector_using_push_back
$ time ./vector_using_push_back 1000000
real 0m0.004s
user 0m0.004s
sys 0m0.000s
- vector_using_insert.cc Создайте пустой вектор, а затем назначьте его элементы в цикле for, используя метод insert
// 3. vector_using_insert.cc
#include <iostream>
#include <vector>
int main(int argc, char *argv[]) {
int N = *argv[1];
std::vector<int> V;
for (int i =0; i < N; i ) {
V.insert(V.begin(), N - i - 1);
}
}
$ g -O0 -std=c 17 vector_using_insert.cc -o vector_using_insert
$ time ./vector_using_insert 1000000
real 0m0.004s
user 0m0.003s
sys 0m0.001s
Как вы можете видеть, все методы в точности равны. Я понимаю, что push_back
это линейно по сложности (когда размер <емкость). В этом примере это явно не так. Разве insert
метод не должен быть линейным?
Я предполагаю, что:
c 17
выполняется ли некоторая оптимизация, хотя я отключил оптимизацию?- На моей машине 2 процессора, я думаю, по 20 ядер каждый, и 32 ГБ оперативной памяти, так что это заставляет вести себя иначе, чем я думаю? Я попробовал 100 000 000, но по-прежнему не видел никаких изменений
Что я здесь делаю не так?
Комментарии:
1. Он все равно может оптимизировать некоторые, например, удалить код, который не имеет никакого заметного эффекта, что означает, что он может оптимизировать все в ваших программах. Кроме того, ваш
N
цикл будет слишком мал, чтобы его можно было заметить даже на современном компьютере.2.
int N = *argv[1];
странно. Обычно вы хотели бы преобразовать строку в int . Я бы предположилN
, что это совсем не то, что вы могли подумать. Распечатайте его. Вероятно, это 49.3. Просто случайно, вы измеряете слишком маленький интервал времени. Было бы более целесообразно запускать тест много раз, чтобы усилить любые различия во времени. Теоретически, ваш тест # 1 имеет O (2N) обхода памяти и одно выделение. В двух других случаях потенциально O (logN) распределений амортизируется до O (N) копий, что равно O (N) обходу. Могут возникнуть проблемы с кэшем, и, как предполагает Ted, возможно, ваш код оптимизирован, потому что вы ничего не делали с данными. Рассмотрим шаг после синхронизации, который усредняет содержимое вектора в volatile.
4. @RetiredNinja, ты угадал. N равно 49.
5. Re: «Я понимаю, что push_back линейен по сложности (когда size < capacity)» — на самом деле
push_back
на удивление всегда постоянное время. Прочитайте о амортизированной временной сложностиstd::vector::push_back
. Вinsert
таких случаях метод также может быть постоянным, но только при вставке в конец вектора. В общем, вставка в конце похожа наpush_back
. Вставка в любом другом месте является линейной, и в вашем случае цикл для вставки в начале является полиномиальным.
Ответ №1:
Как и ожидалось, была ошибка в том, как я преобразовал аргумент stdin в int. Правильный способ:
int N = atoi(argv[1]);
std::cout << "N = " << N << std::endl;
Теперь я вижу некоторые реальные различия:
$ time ./vector_using_push_back 1000000000
N = 1000000000
real 0m20.139s
user 0m16.437s
sys 0m3.701s
$ time ./vector_using_assignment 1000000000
N = 1000000000
real 0m7.530s
user 0m6.005s
sys 0m1.525s
$ time ./vector_using_insert 1000000000
N = 1000000000
# I gave up after a couple of min
Комментарии:
1. Что касается вашего другого вопроса о том, что метод вставки является линейным: обратите внимание, что вы вставляете в начале, что не является линейной операцией
std::vector
. Это объясняет мучительную производительность (потому что ваш тест равен O (N ^ 2)). Для развлечения я собрал аналогичную программу (но выполнил линейную операцию для вставки) здесь: godbolt.org/z/naM35MPPf — Я также добавил четвертый тест, используя рекомендуемый подход резервирования хранилища перед отправкой известного количества данных. Производительность этого более близка к наивной инициализации.2.
atoi
возвращаетint
; нет необходимости приводить результат.