Производительность снижается за «если ошибка, то быстро провал» в C ++? - PullRequest
2 голосов
/ 15 марта 2011

Есть ли разница в производительности (в C ++) между двумя стилями написания if-else, как показано ниже (логически эквивалентный код) для пути likely1 == likely2 == true (likely1 и likely2 означают здесь как заполнители длянекоторые более сложные условия)?

// Case (1):
if (likely1) {
  Foo();
  if (likely2) {
    Bar();
  } else {
    Alarm(2);
  }
} else {
  Alarm(1);
}

против

// Case (2):
if (!likely1) {
  Alarm(1);
  return;
}
Foo();
if (!likely2) {
  Alarm(2);
  return;
}
Bar();

Я был бы очень признателен за информацию о максимально возможном количестве компиляторов и платформ (но с выделенным gcc / x86),

Обратите внимание, что я не заинтересован в удобочитаемости мнений по этим двум стилям, ни в «преждевременной оптимизации» претензий.

РЕДАКТИРОВАТЬ: Другими словами, я хотел бы спросить, считаются ли два стиля в какой-то момент полностью-на 100% эквивалентными / прозрачными для компилятора (например, побитовоэквивалентный AST в какой-то момент в конкретном компиляторе), и если нет, то в чем различия?Для любого (с предпочтением по отношению к «современному» и gcc) компилятора вы знаете.

И, чтобы сделать это более ясным, я тоже не думаю, что это даст мне значительное улучшение производительности, ичто это обычно было бы преждевременной оптимизацией, но я * интересуется и , насколько это может улучшить / ухудшить что-нибудь?

Ответы [ 6 ]

6 голосов
/ 15 марта 2011

Это сильно зависит от компилятора и настроек оптимизации. Если различие имеет решающее значение - реализуйте оба варианта и либо проанализируйте сборку, либо выполните тесты.

4 голосов
/ 15 марта 2011

У меня нет ответов для конкретных платформ, но я могу сделать несколько общих замечаний:

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

  • На современных процессорах, вообще говоря, короткие форвардные ветви не дороги, тогда как неправильно прогнозируемые ветви могут быть дорогими.Под «дорогим», конечно, я подразумеваю несколько циклов

  • Помимо этого, компилятор имеет право упорядочивать базовые блоки, однако ему это нравится, если он не меняет логику.Поэтому, когда вы пишете if (blah) {foo();} else {bar();}, компилятор имеет право выдавать код, подобный следующему:

  evaluate condition blah
  jump_if_true else_label
  bar()
  jump endif_label
else_label:
  foo()
endif_label:

В целом, gcc имеет тенденцию испускать вещи примерно вчтобы ты их написал, при прочих равных.Существуют различные вещи, которые делают все остальное не равным, например, если у вас есть логический эквивалент bar(); return в двух разных местах в вашей функции, gcc вполне может объединить эти блоки, выпустить только один вызов bar() с последующим возвратом,и прыгайте или проваливайтесь туда из двух разных мест.

  • Существует два вида предсказания ветвления - статическое и динамическое.Статический означает, что инструкции ЦП для ветви указывают, является ли условие «вероятным», чтобы ЦП мог оптимизировать работу для общего случая.Компиляторы могут генерировать статические предсказания ветвления на некоторых платформах, и если вы оптимизируете для этой платформы, вы могли бы написать код, чтобы учесть это.Вы можете принять это во внимание, зная, как ваш компилятор обрабатывает различные управляющие структуры, или используя расширения компилятора.Лично я не думаю, что это достаточно последовательно, чтобы обобщать то, что будут делать компиляторы.Посмотрите на разборку.

  • Динамическое прогнозирование ветвлений означает, что в горячем коде ЦП будет вести статистику для себя о том, как вероятны ветвления, и оптимизировать для общего случая.Современные процессоры используют различные методы динамического прогнозирования ветвлений: http://en.wikipedia.org/wiki/Branch_predictor. Код, критичный к производительности, в значительной степени - это горячий код, и, пока работает стратегия динамического прогнозирования ветвлений, он очень быстро оптимизирует горячий код.Могут быть определенные патологические случаи, которые путают конкретные стратегии, но в целом вы можете сказать, что все, что находится в узком цикле, где есть уклон в сторону взятого / не взятого, будет правильно предсказано большую часть времени

  • Иногда даже не имеет значения, правильно ли предсказана ветвь или нет, так как некоторые процессоры в некоторых случаях будут включать в себя обе возможности в конвейере команд, пока он ожидает оценки условия, иугробить ненужный вариант.Современные процессоры получают сложный .Даже в гораздо более простых конструкциях ЦП есть способы избежать затрат на ветвление, такие как условные инструкции для ARM.

  • Вызовы вне линии для других функций в любом случае расстроят все такие догадки.Так что в вашем примере могут быть небольшие различия, и эти различия могут зависеть от фактического кода в Foo, Bar и Alarm.К сожалению, невозможно провести различие между значительными и незначительными различиями или объяснить детали этих функций, не вдаваясь в обвинения в «преждевременной оптимизации», которые вас не интересуют.

  • Почти всегда преждевременно микрооптимизировать код, который еще не написан.Очень сложно предсказать производительность функций с именами Foo и Bar.Предположительно, цель вопроса состоит в том, чтобы определить, есть ли какие-то распространенные ошибки, которые должны определять стиль кодирования.На что ответ таков: благодаря динамическому предсказанию ветвлений, нет.В «горячем» коде очень мало разницы в том, как устроены ваши условия, и в чем разница, когда разницу не так легко предсказать, как «быстрее брать / не брать ветку в состоянии if».

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

1 голос
/ 15 марта 2011

Если вас беспокоит производительность, я полагаю, вы используете оптимизацию по профилю.

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

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

1 голос
/ 15 марта 2011

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

Таким образом, случай 2, как правило, более эффективен, чем случай 1.

В качестве входных данных от встроенных систем реального времени, с которыми я работаю, ваш «случай 2» часто является нормой для кода, который критичен для безопасности и / или производительности. Руководства по стилю для встроенных систем, критически важных для безопасности, часто допускают такой синтаксис, поэтому функция может быстро завершать работу при возникновении ошибок.

Как правило, руководства по стилю будут навязываться синтаксису "case 2", но делают исключение, чтобы разрешить несколько возвратов в одной функции, либо если

1) функция должна быстро выйти и обработать ошибку, или

2) если один единственный возврат в конце функции приводит к меньше читаемого кода, что часто имеет место для различных протоколов и анализаторов данных.

1 голос
/ 15 марта 2011

Это зависит от компилятора.Ознакомьтесь с документацией по gcc на __builtin_expect.Ваш компилятор может иметь что-то подобное.Обратите внимание, что вы действительно должны быть обеспокоены преждевременной оптимизацией.

0 голосов
/ 15 марта 2011

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

Вероятно, как 1, так и 2 вероятно, как следует из их названия.Таким образом, исключение также вероятного сочетания того, что оба являются истинными, будет вероятным самым быстрым:

if(likely1 && likely2)
{
    ... // happens most of the time
}else
{
    if(likely1)
        ...
    if(likely2)
        ...
    else if(!likely1 && !likely2) // happens almost never
        ...
}

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

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...