Вывод vector size() из условия цикла для оптимизации

#c #string #data-structures #stl

#c

Вопрос:

fibs — это std::vector. Используя g , мне посоветовали вывести fibs.size() из цикла, чтобы не вычислять его каждый раз (потому что вектор может измениться)

 int sum = 0;
for(int i = 0; i < fibs.size();   i){
    if(fibs[i] % 2 == 0){
        sum  = fibs[i];
    }
}
  

Конечно, в компиляторе есть какой-то анализ потока данных, который сказал бы нам, что fibs не изменит размер. Есть ли? Или мне следует присвоить какой-либо другой переменной значение fibs.size() и использовать это в условии цикла?

Комментарии:

1. 1 1 2 3 5 … F<sub>n</sub> = F<sub>n 2</sub> — 1.

2. Итак, тот, кто вам посоветовал, не хочет, чтобы size вычислялось каждый раз (вычитание в g перед оптимизацией), но не возражает против вызова operator[] (и дополнительного добавления по сравнению с использованием итератора перед оптимизацией) дважды. Что, и действительно когда-либо. Было бы лучше либо посмотреть на отправленные инструкции, либо рассчитать их время.

3. Стив, если я знаю, что размер вектора гарантированно не изменится, не сэкономит ли это время на использовании operator[]? Кроме того, я клоун c . Итак, использование итератора быстрее?

4. @shuttle87: Я говорю, что если кто-то (кто бы ни дал этот совет) собирается делать дикие обобщения о производительности, я немного удивлен, что они предполагают, что size() это медленнее, чем доступ к переменной, но они не предполагают, что operator[] это медленнее, чем использование итератора или указателя. Я бы не стал точно угадывать, что будет делать оптимизатор — возможно, ему действительно удастся заменить индекс i указателем, но для этого он должен каким-то образом убедить себя, что вектор никогда не перераспределяется, что вряд ли проще, чем убедить себя, что он может повысить значение size .

5. Основная проблема заключается в том, что operator[] придется загружать базовый указатель вектора из векторного объекта (и добавлять индекс), точно так же, как size() в gcc загружаются базовый и конечный указатели из векторного объекта (и вычитаются из них). Таким образом, в обоих случаях это практически один и тот же вид сложной микрооптимизации. Лично я бы не беспокоился ни о том, ни о другом, пока этот код не станет доказанным узким местом, а затем проверил отправленный код. Но если вы собираетесь беспокоиться об одном, я думаю, вам следует беспокоиться об обоих.

Ответ №1:

Компилятор, скорее всего, определит, что оно не изменится. Даже если бы это произошло, size() для векторов это операция O (1).

Комментарии:

1. O (1) говорит только о том, что он ограничен. Это все еще может быть дорогостоящим (ну, не для вектора).

Ответ №2:

Если вы не знаете, что это проблема, оставьте все как есть. Сначала сделайте это правильно, затем сделайте это понятным, затем сделайте это быстро (при необходимости).

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

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

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

Ответ №3:

size () — это операция постоянного времени, при таком вызове штрафа нет. Если вас беспокоит производительность и более общий способ просмотра коллекции, используйте итераторы:

 int sum = 0;
for(auto it = fibs.cbegin(); it != fibs.cend();   it) {
    if((*it) % 2 == 0){
        sum  = *it;
    }
}
  

Ответ №4:

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

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

Ответ №5:

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

 for(int i = 0; i < fibs.size();   i){
    complicated_function();
}
  

complicated_function может измениться fibs . Однако, поскольку приведенный выше код включает вызов функции, компилятор не может сохранить fibs.size() в регистре и, следовательно, вы не можете исключить доступ к памяти.