Что быстрее, итерация STL-вектора с помощью vector:: iterator или с at()?
Что касается производительности, что будет работать быстрее? Есть ли разница? Это зависит от платформы?
//1. Using vector<string>::iterator:
vector<string> vs = GetVector();
for(vector<string>::iterator it = vs.begin(); it != vs.end(); ++it)
{
*it = "Am I faster?";
}
//2. Using size_t index:
for(size_t i = 0; i < vs.size(); ++i)
{
//One option:
vs.at(i) = "Am I faster?";
//Another option:
vs[i] = "Am I faster?";
}
Ответы
Ответ 1
Почему бы не написать тест и не узнать?
Изменить: Мое плохое - я думал, что я выбрал оптимизированную версию, но не был. На моей машине, скомпилированной с g++ -O2, версия итератора немного медленнее, чем версия operator [], но, вероятно, не так значительно.
#include <vector>
#include <iostream>
#include <ctime>
using namespace std;
int main() {
const int BIG = 20000000;
vector <int> v;
for ( int i = 0; i < BIG; i++ ) {
v.push_back( i );
}
int now = time(0);
cout << "start" << endl;
int n = 0;
for(vector<int>::iterator it = v.begin(); it != v.end(); ++it) {
n += *it;
}
cout << time(0) - now << endl;
now = time(0);
for(size_t i = 0; i < v.size(); ++i) {
n += v[i];
}
cout << time(0) - now << endl;
return n != 0;
}
Ответ 2
Использование итератора приводит к увеличению указателя (для увеличения) и разыменованию разыменования указателя.
С индексом приращение должно быть одинаково быстрым, но поиск элемента включает добавление (указатель данных + индекс) и разыменование этого указателя, но разница должна быть предельной.
at()
также проверяет, находится ли индекс в границах, поэтому он может быть медленнее.
Результаты тестов для 500M итераций, размер вектора 10, с gcc 4.3.3 (-O3), linux 2.6.29.1 x86_64:
at()
: 9158ms
operator[]
: 4269ms
iterator
: 3914ms
YMMV, но если использование индекса делает код более удобочитаемым/понятным, вы должны это сделать.
Ответ 3
Если вам не нужна индексация, не используйте его. Концепция итератора существует для вашего наилучшего. Итераторы очень легко оптимизируются, а прямой доступ требует дополнительных знаний.
Индексирование предназначено для прямого доступа. Скобки и метод at
делают это. at
будет, в отличие от []
, проверять отсутствие индексации, поэтому он будет медленнее.
Кредо: не спрашивайте, что вам не нужно. Тогда компилятор не будет взимать плату за то, что вы не используете.
Ответ 4
Поскольку вы смотрите на эффективность, вы должны понимать, что следующие варианты потенциально более эффективны:
//1. Using vector<string>::iterator:
vector<string> vs = GetVector();
for(vector<string>::iterator it = vs.begin(), end = vs.end(); it != end; ++it)
{
//...
}
//2. Using size_t index:
vector<string> vs = GetVector();
for(size_t i = 0, size = vs.size(); i != size; ++i)
{
//...
}
так как функция end/size вызывается только один раз, а не каждый раз через цикл. Вероятно, компилятор все равно будет встраивать эти функции, но этот способ гарантирует.
Ответ 5
Как говорят все остальные, делайте тесты.
Сказав это, я бы сказал, что итератор работает быстрее, так как at() также проверяет диапазон, т.е. он выдает исключение out_of_range, если индекс выходит за пределы. Эта проверка сама по себе может вызвать некоторые накладные расходы.
Ответ 6
Я бы предположил, что первый вариант быстрее.
Но это зависит от реализации. Чтобы убедиться, что вы должны создать собственный код.
Зачем формировать собственный код?
Потому что все эти факторы будут меняться:
- Какая ОС
- Какой компилятор
- Какая реализация STL использовалась
- Включены ли оптимизации?
- ... (другие факторы)
Ответ 7
Первый режим будет быстрее в режиме отладки, потому что доступ к индексу создает итераторы за сценой, но в режиме выпуска, где все должно быть встроено, разница должна быть незначительной или null
Ответ 8
Вы можете использовать этот тестовый код и сравнить результаты!
Dio it!
#include <vector>
#include <iostream>
#include <ctime>
using namespace std;;
struct AAA{
int n;
string str;
};
int main() {
const int BIG = 5000000;
vector <AAA> v;
for ( int i = 0; i < BIG; i++ ) {
AAA a = {i, "aaa"};
v.push_back( a );
}
clock_t now;
cout << "start" << endl;
int n = 0;
now = clock();
for(vector<AAA>::iterator it = v.begin(); it != v.end(); ++it) {
n += it->n;
}
cout << clock() - now << endl;
n = 0;
now = clock();
for(size_t i = 0; i < v.size(); ++i) {
n += v[i].n;
}
cout << clock() - now << endl;
getchar();
return n != 0;
}
Ответ 9
Это действительно зависит от того, что вы делаете, но если вам нужно продолжать повторное объявление итератора, Итераторы становятся MARGINALLY SLOWER. В моих тестах самая быстрая возможная итерация заключалась бы в том, чтобы объявить простой * вашему массиву векторов и пройти через это.
например:
Итерация вектора и вытягивание двух функций за проход.
vector<MyTpe> avector(128);
vector<MyTpe>::iterator B=avector.begin();
vector<MyTpe>::iterator E=avector.end()-1;
for(int i=0; i<1024; ++i){
B=avector.begin();
while(B!=E)
{
float t=B->GetVal(Val1,12,Val2); float h=B->GetVal(Val1,12,Val2);
++B;
}}
Вектор Взял 90 кликов (0.090000 секунд)
Но если вы сделали это с указателями...
for(int i=0; i<1024; ++i){
MyTpe *P=&(avector[0]);
for(int i=0; i<avector.size(); ++i)
{
float t=P->GetVal(Val1,12,Val2); float h=P->GetVal(Val1,12,Val2);
}}
Vector заработал 18 кликов (0.018000 секунд)
Что примерно эквивалентно...
MyTpe Array[128];
for(int i=0; i<1024; ++i)
{
for(int p=0; p<128; ++p){
float t=Array[p].GetVal(Val1, 12, Val2); float h=Array[p].GetVal(Val2,12,Val2);
}}
Массив Взял 15 кликов (0.015000 секунд).
Если вы устраните вызов avector.size(), время станет таким же.
Наконец, вызов с помощью []
for(int i=0; i<1024; ++i){
for(int i=0; i<avector.size(); ++i){
float t=avector[i].GetVal(Val1,12,Val2); float h=avector[i].GetVal(Val1,12,Val2);
}}
Vector Взял 33 клика (0.033000 секунд)
Сроки с часами()
Ответ 10
Это зависит.
Ответ гораздо более тонкий, чем показывают существующие ответы.
at
всегда медленнее, чем итераторы или operator[]
.
Но для operator[]
против итераторов это зависит от:
-
Как именно вы используете operator[]
.
-
У вашего конкретного процессора есть индексные регистры (ESI/EDI
на x86).
-
Сколько другого кода также использует тот же самый индекс, переданный в operator[]
.
(например, вы индексируете через несколько массивов в стоп-стоп?)
Вот почему:
-
Если вы делаете что-то вроде
std::vector<unsigned char> a, b;
for (size_t i = 0; i < n; ++i)
{
a[13 * i] = b[37 * i];
}
Тогда этот код будет намного медленнее, чем версия итератора, так как он выполняет операцию умножения на каждой итерации цикла!
Аналогично, если вы делаете что-то вроде:
struct T { unsigned char a[37]; };
std::vector<T> a;
for (size_t i = 0; i < n; ++i)
{
a[i] = foo(i);
}
Тогда это, вероятно, будет также медленнее, чем версия итератора, потому что sizeof(T)
не является степенью 2, и поэтому вы (снова) умножаетесь на 37
каждый раз, когда вы зацикливаете!
-
Если ваш процессор имеет индексные регистры, то ваш код может работать также или даже лучше с индексами, а не с помощью итераторов, если использование индекса индекса освобождает другой регистр для использования в цикле. Это не то, что вы можете сказать, просто глядя; вам придется профилировать код и/или дизассемблировать его.
-
Если несколько массивов могут совместно использовать один и тот же индекс, тогда код должен только увеличивать один индекс, а не увеличивать количество итераторов, что уменьшает количество записей в памяти и, как следствие, повышает производительность. Однако, если вы только итерации по одному массиву, то итератор может быть очень быстрым, так как он избегает необходимости добавлять смещение к существующему базовому указателю.
В общем, вы должны отсылать итераторы к индексам и индексам указателям до тех пор, пока вам не удастся столкнуться с узким местом, которое показывает профилирование, будет полезно переключиться, поскольку итераторы являются универсальными и уже вероятно, будет самым быстрым подходом; они не требуют, чтобы данные были случайным образом адресованы, что позволяет при необходимости заменять контейнеры. Индексы являются следующим предпочтительным инструментом, так как они по-прежнему не требуют прямого доступа к данным - они становятся недействительными реже, и вы можете, например, замените a deque
на a vector
без каких-либо проблем. Указатели должны быть последним средством, и они будут полезны только в том случае, если итераторы еще не вырождаются в потенциометры в режиме выпуска.
Ответ 11
Я думаю, что единственным ответом может быть тест на вашей платформе. Как правило, единственное, что стандартизовано в STL, это тип итераторов, предлагаемых коллекцией, и сложность алгоритмов.
Я бы сказал, что между этими двумя версиями нет (не так уж много) - единственное различие, которое я мог бы подумать, - это то, что код должен перебирать всю коллекцию, когда ему приходится вычислять длину array (я не уверен, что длина сохраняется в переменной внутри вектора, тогда накладные расходы не имеют значения)
Доступ к элементам с "at" должен занять немного больше времени, чем прямой доступ к нему с помощью [], поскольку он проверяет, находитесь ли вы в границах вектора и выбрасывает исключение, если вы находитесь за пределами границ (кажется [] как правило, просто использует арифметику указателя - поэтому он должен быть быстрее)
Ответ 12
Если вы используете VisualStudio 2005 или 2008, чтобы получить наилучшую производительность из вектора, вам нужно определить
_SECURE_SCL = 0
По умолчанию используется _SECURE_SCL, что делает итерацию над помещением значительно медленнее. Тем не менее, оставьте это в отладочных сборках, это значительно облегчит отслеживание любых ошибок. Одно слово предостережения, так как макрос изменяет размер итераторов и контейнеров, вы должны быть согласованными во всех единицах компиляции, которые совместно используют контейнер stl.
Ответ 13
Я нашел эту нить сейчас, пытаясь оптимизировать мой код OpenGL и хочу поделиться своими результатами, даже если поток старый.
Фон: У меня есть 4 вектора размером от 6 до 12. Запись происходит только один раз в начале кода, и чтение происходит для каждого из элементов в векторах каждые 0,1 миллисекунды
Ниже приведена урезанная версия используемого кода:
for(vector<T>::iterator it = someVector.begin(); it < someVector.end(); it++)
{
T a = *it;
// Various other operations
}
Частота кадров с использованием этого метода составляла около 7 кадров в секунду (fps).
Однако, когда я изменил код на следующий, частота кадров почти удвоилась до 15 кадров в секунду.
for(size_t index = 0; index < someVector.size(); ++index)
{
T a = someVector[index];
// Various other operations
}
Ответ 14
Разница должна быть незначительной. std::vector гарантирует, что его элементы будут последовательно размещены в памяти. Поэтому большинство реализаций stl реализуют итераторы в std::vector как простой указатель. Поскольку это ум, единственная разница между этими двумя версиями должна заключаться в том, что первый увеличивает указатель, а во втором - индекс, который затем добавляется к указателю. Таким образом, моя догадка была бы второй, может быть, одной чрезвычайно быстрой (с точки зрения циклов) машинной инструкцией больше.
Попробуйте проверить код машины, который производит ваш компилятор.
В целом, однако, совет должен был бы профиль, если это действительно имеет значение. Размышление о таком вопросе преждевременно обычно не дает вам слишком многого. Как правило, ваши горячие точки кода будут в другом месте, где вы можете не подозревать об этом с первого взгляда.
Ответ 15
Вот код, который я написал, скомпилированный в Code:: Blocks v12.11, используя компилятор mingw по умолчанию.
Это создает огромный вектор, затем обращается к каждому элементу с помощью итераторов,() и индекса.
Каждый цикл зацикливается один раз, вызывая последний элемент по функции и один раз, сохраняя последний элемент во временную память.
Сроки выполняются с использованием GetTickCount.
#include <iostream>
#include <windows.h>
#include <vector>
using namespace std;
int main()
{
cout << "~~ Vector access speed test ~~" << endl << endl;
cout << "~ Initialization ~" << endl;
long long t;
int a;
vector <int> test (0);
for (int i = 0; i < 100000000; i++)
{
test.push_back(i);
}
cout << "~ Initialization complete ~" << endl << endl;
cout << " iterator test: ";
t = GetTickCount();
for (vector<int>::iterator it = test.begin(); it < test.end(); it++)
{
a = *it;
}
cout << GetTickCount() - t << endl;
cout << "Optimised iterator: ";
t=GetTickCount();
vector<int>::iterator endofv = test.end();
for (vector<int>::iterator it = test.begin(); it < endofv; it++)
{
a = *it;
}
cout << GetTickCount() - t << endl;
cout << " At: ";
t=GetTickCount();
for (int i = 0; i < test.size(); i++)
{
a = test.at(i);
}
cout << GetTickCount() - t << endl;
cout << " Optimised at: ";
t = GetTickCount();
int endof = test.size();
for (int i = 0; i < endof; i++)
{
a = test.at(i);
}
cout << GetTickCount() - t << endl;
cout << " Index: ";
t=GetTickCount();
for (int i = 0; i < test.size(); i++)
{
a = test[i];
}
cout << GetTickCount() - t << endl;
cout << " Optimised Index: ";
t = GetTickCount();
int endofvec = test.size();
for (int i = 0; i < endofvec; i++)
{
a = test[i];
}
cout << GetTickCount() - t << endl;
cin.ignore();
}
Исходя из этого, я лично получил, что "оптимизированные" версии быстрее, чем "неоптимизированные". Итераторы медленнее, чем vector.at(), которая медленнее прямых индексов.
Я предлагаю вам скомпилировать и запустить код для себя.
EDIT: этот код был написан, когда у меня было меньше опыта с C/С++. Еще одним тестовым примером должно быть использование операторов приращения префикса вместо постфикса. Это должно улучшить время работы.
Ответ 16
Только слегка касательный к исходному вопросу, но самый быстрый цикл будет
for( size_t i=size() ; i-- ; ) { ... }
который, конечно, будет отсчитываться. Это дает существенную экономию, если в вашем цикле имеется большое количество итераций, но оно содержит лишь небольшое количество очень быстрых операций.
Таким образом, с доступом оператора [] это может быть быстрее, чем многие из уже опубликованных примеров.