Подтвердить что ты не робот

Должен ли я оптимизировать или позволить компилятору сделать это?

Каков предпочтительный метод написания циклов в соответствии с эффективностью: Способ a)

   /*here I'm hoping that compiler will optimize this  
 code and won't be calling size every time it iterates through this loop*/
    for (unsigned i = firstString.size(); i < anotherString.size(), ++i)
    {
    //do something
    }

или, может быть, мне следует сделать это следующим образом: Путь b)

unsigned first = firstString.size();
unsigned second = anotherString.size();

и теперь я могу написать:

    for (unsigned i = first; i < second, ++i)
    {
    //do something
    }

второй способ кажется мне худшим вариантом по двум причинам: область загрязнения и многословие, но у нее есть преимущество в том, что size() будет вызываться один раз для каждого объекта.
Ждем ваших ответов.

4b9b3361

Ответ 1

Я обычно пишу этот код как:

/* i and size are local to the loop */
for (size_t i = firstString.size(), size = anotherString.size(); i < size; ++i) {
  //do something
}

таким образом я не загрязняю родительскую область и не вызываю anotherString.size() для каждой итерации цикла.

это особенно полезно для итераторов:

for(some_generic_type<T>::forward_iterator it = collection.begin(), end = collection.end();
    it != end; ++it) {
   // do something with *it
}

Ответ 2

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

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

Ответ 3

Сначала я бы использовал первую версию, просто потому, что она выглядит более чистой и удобной для ввода. Затем вы можете просмотреть его, чтобы убедиться, что что-то нужно оптимизировать.

Но я очень сомневаюсь, что первая версия приведет к заметному снижению производительности. Если контейнер реализует size() следующим образом:

inline size_t size() const
{
    return _internal_data_member_representing_size;
}

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

Ответ 4

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

Такая оптимизация действительно небезопасна с точки зрения компилятора, вам нужно сделать это самостоятельно. Выполнение самостоятельно не означает, что вам нужно ввести две дополнительные локальные переменные. В зависимости от вашей реализации размера это может быть операция O (1). Если размер также объявлен встроенным, вы также освободите вызов функции, сделав вызов size() таким же хорошим, как доступ к локальному члену.

Ответ 5

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

Ответ 6

Это одна из тех вещей, которые вы должны испытать сами. Запустите циклы 10 000 или даже 100 000 итераций и посмотрите, какая разница, если таковая имеется, существует.

Это должно рассказать вам все, что вы хотите знать.

Ответ 7

Моя рекомендация состоит в том, чтобы позволить незначительным оптимизациям проникнуть в ваш стиль. Что я имею в виду, так это то, что если вы узнаете более оптимальный способ сделать что-то, и вы не увидите каких-либо его недостатков (в отношении удобства, удобочитаемости и т.д.), Вы можете также принять его.

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

Ответ 8

Я надеюсь, что компилятор оптимизирует это...

Вы не должны. Все, что связано с

  • Вызов неизвестной функции или
  • Вызов метода, который может быть переопределен

сложно оптимизировать компилятор С++. Вам может повезти, но вы не можете рассчитывать на это.

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

Ответ 9

Вот как я смотрю на это. Производительность и стиль важны, и вам нужно выбирать между ними.

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

Ответ 10

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

Вы можете потратить часы, пытаясь улучшить один цикл, только чтобы получить повышение производительности на 0,001%. Если вас беспокоит производительность - используйте профилировщики.

Ответ 11

Нет ничего плохого в этом пути (б), если вы просто хотите написать что-то, что, вероятно, будет не хуже, чем путь (а) и, возможно, быстрее. Это также делает более понятным, что вы знаете, что размер строки останется неизменным.

Компилятор может или не может заметить, что size останется постоянным; на всякий случай, вы могли бы также выполнить эту оптимизацию самостоятельно. Я бы, конечно, сделал это, если бы был подозрительным, что код, который я писал, будет запущен много, даже если бы я не был уверен, что это будет большой проблемой. Это очень просто сделать, для этого требуется не более 10 дополнительных секунд, это очень маловероятно, чтобы замедлить работу, и, если ничего другого, почти наверняка заставит неоптимизированную сборку работать немного быстрее.

(Кроме того, переменная first в стиле (b) не нужна, код для выражения init запускается только один раз.)

Ответ 12

  • Сколько процентов времени потрачено на for, а не на // do something? (Не угадывайте - пробуйте его.) Если это & ​​lt; 10% у вас, вероятно, больше проблем в других местах.

  • Все говорят: "Составители настолько умны в наши дни". Ну, они не умнее, чем бедные кодеры, которые их пишут. Вы тоже должны быть умными. Может быть, компилятор может оптимизировать его, но почему не искушать его?

Ответ 13

Для функции члена std:: size_t size() const, которая не только является O (1), но также объявлена ​​ "const", и поэтому ее можно автоматически вытащить из цикла компилятором, возможно,. Тем не менее, я бы не стал рассчитывать на компилятор, чтобы удалить его из цикла, и я думаю, что это хорошая привычка входить в факторные вызовы в цикле для случаев, когда функция не является постоянной или O (1). Кроме того, я думаю, что назначение значений переменной приводит к тому, что код становится более читаемым. Я бы не предложил, однако, что вы делаете какие-то преждевременные оптимизации, если это приведет к тому, что код будет более трудным для чтения. Опять же, я думаю, что следующий код более читабельен, поскольку в цикле читать меньше:

 std::size_t firststrlen = firststr.size();
 std::size_t secondstrlen = secondstr.size();
 for ( std::size_t i = firststrlen; i < secondstrlen; i++ ){
      // ...
 }

Кроме того, я должен указать, что вместо "unsigned" следует использовать "std:: size_t" , поскольку тип "std:: size_t" может варьироваться от одной платформы к другой, а использование "unsigned" может привести к на сундуки и ошибки на платформах, для которых тип "std:: size_t" "unsigned long" вместо "unsigned int".