Насколько порядок ярлыков случаев влияет на эффективность операторов switch?

Рассмотрим:

if (condition1)
{
    // Code block 1
}
else
{
    // Code block 2
}

Если я знаю, что condition1 будет true большую часть времени, тогда я должен закодировать логику как написанную, а не:

if (!condition1)
{
    // Code block 2
}
else
{
    // Code block 1
}

так как я буду избегать штрафа jump ко второму блоку кода (примечание: у меня ограниченное знание языка ассемблера). Передает ли эта идея выражениям switch и case меток?

switch (myCaseValue)
{
    case Case1:
        // Code block 1
        break;

    case Case2:
        // Code block 2
        break;

    // etc.
}

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

Ответы

Ответ 1

Некоторые факты для современного оборудования, такого как x86 или x86_64:

  • Безусловно принятая ветвь почти не требует дополнительных затрат, помимо декодирования. Если вам нужен номер, это примерно четверть тактового цикла.
  • Условная ветвь, которая была правильно предсказана, почти не имеет дополнительных затрат.
  • Условная ветвь, которая не была правильно предсказана, имеет штраф, равный длине процессорных конвейеров, это примерно 12-20 часов в зависимости от аппаратного обеспечения.
  • Механизмы прогнозирования очень сложны. Петли с небольшим количеством итераций (на Core 2, например, до 64) могут быть прекрасно предсказаны. Небольшие повторяющиеся шаблоны, такие как "взятый-взятый-nottaken-take", могут быть предсказаны, если они не слишком длинны (IIRC 6 на Core2).

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

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

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

Ответ 2

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

Как обычно, см. wikipedia: Branch Predictor

Ответ 3

Это зависит. Компилятор будет использовать кучу внутренних критериев, зависящих от реализации, чтобы решить, следует ли реализовать switch как последовательность if-подобных тестов или как таблицу перехода. Это может зависеть, например, от того, насколько "компактен" ваш набор ярлыков case. Если ваши значения метки case образуют "плотный" набор, скорее всего, компилятор использует таблицу переходов, и в этом случае упорядочение меток case не имеет значения. Если он решит пойти с тем, что напоминает последовательность тестов if-else, порядок может иметь значение.

Имейте в виду, что тело switch - это один большой оператор, причем case метки содержат несколько точек входа в этот оператор. По этой причине способность компиляторов (а также ваша) переупорядочить подблоки case внутри этого оператора может быть ограничена.

Ответ 4

Ящики меток должны быть упорядочены наиболее эффективным способом для удобства чтения.

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

Ответ 5

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

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

Теперь, что касается оператора switch, я всегда буду использовать switch, когда он сделает код более читаемым. Хуже того, что компилятор должен делать с оператором switch, который эквивалентен оператору if, - это сгенерировать тот же код. Для более чем нескольких случаев операторы switch обычно компилируются как таблица перехода. Но опять же набор тестов if, которые сравнивают одну переменную с набором значений, вполне может быть распознан компилятором таким образом, что он будет делать то же самое. Тем не менее, я бы предположил, что использование коммутатора позволит компилятору более легко распознать ситуацию.

Если вам действительно интересно получить максимальную отдачу от этого условного, вы можете использовать что-то вроде MSVC Profile Guided Optimization (PGO или 'pogo'), который использует результаты прогонов профилирования для оптимизации генерации условных выражений. Я не знаю, имеет ли GCC аналогичные возможности.

Ответ 6

Я не уверен в компиляторе С#, но я знаю, что в сборке оператор switch может быть запрограммирован как переход к определенной строке, вместо того, чтобы оценивать выражение как оператор if. Так как в select вы имеете все константы, он просто рассматривает случаи как номера строк, и вы сразу переходите к номеру строки (значение case) без какой-либо оценки. Это делает порядок высказываний дела действительно неважным.

Ответ 7

Я предполагаю, что вы знаете, что это будет иметь значение только в том случае, если это горячая точка. Лучший способ узнать, является ли это хот-спотом, чтобы запустить код, пробовать счетчик программ и посмотреть, есть ли там более 10% времени. Если это горячая точка, посмотрите, сколько времени потрачено на выполнение if или switch. Обычно это незначительно, если только Block 1 и/или Block 2 ничего не делают. Для этого вы можете использовать профилировщик. Я просто несколько раз останавливаю его.

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

Ответ 8

Как говорили другие, это зависит от множества вещей, в том числе от количества случаев, от того, как выполняется оптимизация, и от архитектуры, на которой вы работаете. Для интересного обзора см. http://ols.fedoraproject.org/GCC/Reprints-2008/sayle-reprint.pdf

Ответ 9

Если вы сначала ставите случаи, которые случаются чаще всего, это немного оптимизирует код, и из-за того, что действия переключателей работают одинаково, это правда. Когда программа переходит в переключатель и находит случай, что true, он выполнит его и ударит break, который выйдет из цикла. Ваше мышление верное.

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