Скорость доступа к std :: vector с помощью итератора по сравнению с оператором [] / index? - PullRequest
35 голосов
/ 26 марта 2010

Скажи, у меня есть

std::vector<SomeClass *> v;

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

Какой самый быстрый тип доступа между этими двумя?

Доступ к итератору:

std::vector<SomeClass *> v;
std::vector<SomeClass *>::iterator i;
std::vector<SomeClass *>::reverse_iterator j;

// i loops forward, j loops backward
for( i = v.begin(), j = v.rbegin(); i != v.end() && j != v.rend(); i++, j++ ){
    // some operations on v items
}

Доступ по индексам (по индексу)

std::vector<SomeClass *> v;
unsigned int i, j, size = v.size();

// i loops forward, j loops backward
for( i = 0, j = size - 1; i < size && j >= 0; i++, j-- ){
    // some operations on v items
}

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

Ответы [ 11 ]

27 голосов
/ 26 марта 2010

Разница в производительности, вероятно, незначительна или отсутствует (компилятор может оптимизировать их, чтобы они были идентичными); Вы должны беспокоиться о других вещах, например, о правильности вашей программы (медленная, но правильная программа лучше, чем быстрая и неправильная программа). Существуют и другие преимущества использования итераторов, такие как возможность изменить базовый контейнер на контейнер без operator[] без изменения ваших циклов. См. этот вопрос для более.

const_iterators, скорее всего, не будут иметь разницы в производительности или пренебрежимо малы по сравнению с обычными итераторами. Они предназначены для улучшения правильности вашей программы, предотвращая изменение вещей, которые не должны быть изменены, а не для повышения производительности. То же самое относится и к ключевому слову const в целом.

Короче говоря, оптимизация не должна беспокоить вас, пока не произойдут две вещи: 1) вы заметили, что она работает слишком медленно и 2) вы профилировали узкие места . Для 1), если он работал в десять раз медленнее, чем мог, но работает ли он только один раз и занимает 0,1 мс, кого это волнует? Во-вторых, убедитесь, что это определенно узкое место, в противном случае его оптимизация будет иметь почти незначительное влияние на производительность!

17 голосов
/ 29 сентября 2013

Выполнен простой тест на основе петель. Я использовал VS 2010 SP1 (конфигурация выпуска).

  1. Использовать итераторы (* it = * it + 1;)
  2. Использовать индексы (против [i] = против [i] + 1;)

За несколько миллиардов итераций второй подход оказался немного быстрее, на 1%. Результат (индексы немного быстрее, чем итераторы) воспроизводим, но разница, как я уже сказал, очень мала.

4 голосов
/ 26 марта 2010

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

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

3 голосов
/ 07 августа 2015

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

  TimeSpent([](){
    std::vector<int> vt = { 10, 9, 8, 7, 6, 5, 4, 3, 2, 1 };
    for (int i = int(vt.size()) - 1; i >= 0; i--)
    {
      if (vt[i] % 2 == 0)
      {
        //cout << "removing " << vt[i] << endl;
        vt.erase(vt.begin() + i);
      }
    }
  });

Следующий код относится к элементам вектора доступа с использованием итератора

  TimeSpent([](){
    std::vector<int> vt = { 10, 9, 8, 7, 6, 5, 4, 3, 2, 1 };
    for (std::vector<int>::iterator num = vt.begin(); num != vt.end();)
    {
      if (*num % 2 == 0)
      {
        num = vt.erase(num);
      }
      else
      {
        ++num;
      }
    }
  });

Проверено путем вызова их этой функцией отдельно

void TimeSpent(std::function<void()> func)
{
  const int ONE_MIL = 10000;
  long times = ONE_MIL;
  std::chrono::steady_clock::time_point start = std::chrono::steady_clock::now();
  while (times > 0)
  {
    func();
    --times;
  }
  std::chrono::steady_clock::time_point end = std::chrono::steady_clock::now();
  cout << "time elapsed : " << std::chrono::duration_cast<std::chrono::milliseconds>(end - start).count() << endl;
}


Тестируемой средой является Visual Studio 2013 Pro. версия 4.5.51650
Результаты:
оператор []: 192
итератор: 212
Резюме: когда мы получаем доступ к векторному контейнеру, operator [] работает быстрее, чем итератор.

2 голосов
/ 26 марта 2010

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

1 голос
/ 08 февраля 2013

При оптимизации (-O2) время должно улучшиться (должно быть почти идентично).

1 голос
/ 26 марта 2010

Как всегда, это зависит. Обычно я не думаю, что вы увидите какую-либо разницу, но только вы можете определить это, профилировав свой код. Некоторые компиляторы реализуют векторные итераторы как необработанные указатели, а некоторые нет. Кроме того, в отладочных сборках некоторые компиляторы могут использовать проверенный итератор, который может работать медленнее. Но в производственном режиме все не может быть иначе. Профилируйте его и смотрите.

0 голосов
/ 04 июня 2012

Я запутался в чем-то подобном и написал программу для проверки производительности: https://github.com/rajatkhanduja/Benchmarks/blob/master/C%2B%2B/vectorVsArray.cpp

Вот соответствующие наблюдения для чтения / записи в вектор размером 1 м с использованием g ++ (без каких-либо флагов оптимизации) на Linux-i686 (64-разрядная машина) с 7,7 ГБ ОЗУ: -

Время, необходимое для записи в вектор с использованием индексов. : 11,3909 мс

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

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

Время, необходимое для записи в вектор с использованием итераторов (последовательно). : 24,9949 мс

Время, затрачиваемое на чтение из вектора с использованием итераторов (последовательно). : 18,8049 мс

0 голосов
/ 20 апреля 2010

Я бы пошел на итераторы, но я бы оптимизировал вызов end() в цикле и изменил бы преинкремент на постинкремент. То есть Я бы

std::vector<SomeClass *> v;
std::vector<SomeClass *>::iterator i,ie;
std::vector<SomeClass *>::reverse_iterator j,je;

// i loops forward, j loops backward
for( i=v.begin(),ie=v.end(), j=v.rbegin(),je=v.rend(); i!=ie && j!=je; ++i,++j ){
    // some operations on v items
}

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

0 голосов
/ 26 марта 2010

Вы не только преждевременно оптимизируете, но и микрооптимизируете. Это зло почти такое же плохое, как и первое (с той лишь разницей, что очень, очень, очень редко необходимо микрооптимизировать) Соедините их вместе, и вы получите рецепт катастрофы.

Если вы запустите профилировщик и обнаружите, что эта область кода является узким местом, вам нужно будет оптимизировать. Вы не оптимизируете, пытаясь уменьшить цикл с 23 тактов до 22. Вы оптимизируете, находя способы уменьшить O () вашего алгоритма. Но пока вы не запустите профилировщик, вы должны уделять больше внимания дизайну, чем другим.

...