2010-07-17 2 views
33

Во время оптимизации программы, пытаясь оптимизировать цикл, который выполняет итерацию через вектор, я обнаружил следующий факт: :: std :: vector :: at() EXTREMELY медленнее, чем оператор []!Почему std :: vector :: operator [] от 5 до 10 раз быстрее, чем std :: vector :: at()?

Оператор [] составляет от 5 до 10 раз быстрее, чем при(), как в выпуске & отладки сборок (VS2008 x 86).

Прочитав немного в Интернете, я понял, что при() проверка границ. Хорошо, но, замедляя работу до 10 раз ?!

Есть ли причина для этого? Я имею в виду, что проверка границ - это простое сравнение чисел, или я что-то упускаю?
Вопрос в том, что является реальной причиной этого удара производительности?
Далее, есть ли способ сделать это еще быстрее?

Я, конечно, собираюсь поменять все мои вызовы с помощью [] в других частях кода (в которых у меня уже есть пользовательская проверка границ!).

Доказательство концепции:

#define _WIN32_WINNT 0x0400 
#define WIN32_LEAN_AND_MEAN 
#include <windows.h> 

#include <conio.h> 

#include <vector> 

#define ELEMENTS_IN_VECTOR 1000000 

int main() 
{ 
    __int64 freq, start, end, diff_Result; 
    if(!::QueryPerformanceFrequency((LARGE_INTEGER*)&freq)) 
     throw "Not supported!"; 
    freq /= 1000000; // microseconds! 

    ::std::vector<int> vec; 
    vec.reserve(ELEMENTS_IN_VECTOR); 
    for(int i = 0; i < ELEMENTS_IN_VECTOR; i++) 
     vec.push_back(i); 

    int xyz = 0; 

    printf("Press any key to start!"); 
    _getch(); 
    printf(" Running speed test..\n"); 

    { // at() 
     ::QueryPerformanceCounter((LARGE_INTEGER*)&start); 
     for(int i = 0; i < ELEMENTS_IN_VECTOR; i++) 
      xyz += vec.at(i); 
     ::QueryPerformanceCounter((LARGE_INTEGER*)&end); 
     diff_Result = (end - start)/freq; 
    } 
    printf("Result\t\t: %u\n\n", diff_Result); 

    printf("Press any key to start!"); 
    _getch(); 
    printf(" Running speed test..\n"); 

    { // operator [] 
     ::QueryPerformanceCounter((LARGE_INTEGER*)&start); 
     for(int i = 0; i < ELEMENTS_IN_VECTOR; i++) 
      xyz -= vec[i]; 
     ::QueryPerformanceCounter((LARGE_INTEGER*)&end); 
     diff_Result = (end - start)/freq; 
    } 

    printf("Result\t\t: %u\n", diff_Result); 
    _getch(); 
    return xyz; 
} 

Edit:
Теперь значение в настоящее время assiged к "XYZ", так что компилятор не будет "стереть" его.

+0

Возможно, вы должны что-то делать с элементами вместо того, чтобы просто запрашивать их, или компилятор мог бы его оптимизировать. – schnaader

+0

Не понял тебя. Объяснить? – Poni

+1

Попробуйте сделать что-то вроде 'test_int + = vec [i]' в циклах for. Поскольку вы ничего не делаете с векторным элементом, компилятор может полностью его оптимизировать. Также см. Ответ Бена для этого. – schnaader

ответ

51

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

Если вы можете доказать, что индекс находится в пределах диапазона без проверки времени выполнения, используйте operator[]. В противном случае используйте at() или добавьте собственную проверку перед доступом. operator[] должен быть как можно более быстрым, но будет беспорядочно взорваться, если индекс недействителен.

+25

Ну, он взорвется, если повезет. :) –

3

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

25

Я побежал тестовый код на моей машине:

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

В оптимизированной версии релиза второй цикл цикла полностью оптимизирован (вызов operator[], скорее всего, встроен, и оптимизатор может видеть, что цикл ничего не делает и может удалить весь цикл).

Если я изменяю тело петель, чтобы выполнить некоторую фактическую работу, например, vec.at(i)++; и vec[i]++; соответственно, разница между двумя петлями несущественна.

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

+0

Он запускает отладочную сборку с включенной отладкой итератора. –

+0

@Hans: Мой тест был с настройками по умолчанию, поэтому отладка итератора была включена в сборке отладки (однако, я думаю, это ожидаемый результат - они будут выполняться примерно одинаково), поскольку он позволяет связать проверки для 'op [ ] '). Если я отключу отладку итератора, он даст 'op []' примерно двукратное увеличение производительности над 'at()'. (Когда я отправил ответ изначально, я не особо фокусировался на производительности отладки, но вы правы: отладка итератора может существенно повлиять на сборку отладки). –

Смежные вопросы