Как лучше всего использовать switch
оператор против использования if
оператора для 30unsigned
перечислений, где около 10 имеют ожидаемое действие (в настоящее время это то же самое действие). Производительность и пространство необходимо учитывать, но это не критично. Я абстрагировал фрагмент, так что не ненавидите меня за соглашения об именах.
switch
заявление:
// numError is an error enumeration type, with 0 being the non-error case
// fire_special_event() is a stub method for the shared processing
switch (numError)
{
case ERROR_01 : // intentional fall-through
case ERROR_07 : // intentional fall-through
case ERROR_0A : // intentional fall-through
case ERROR_10 : // intentional fall-through
case ERROR_15 : // intentional fall-through
case ERROR_16 : // intentional fall-through
case ERROR_20 :
{
fire_special_event();
}
break;
default:
{
// error codes that require no additional action
}
break;
}
if
заявление:
if ((ERROR_01 == numError) ||
(ERROR_07 == numError) ||
(ERROR_0A == numError) ||
(ERROR_10 == numError) ||
(ERROR_15 == numError) ||
(ERROR_16 == numError) ||
(ERROR_20 == numError))
{
fire_special_event();
}
Ответы:
Используйте переключатель.
В худшем случае компилятор сгенерирует тот же код, что и цепочка if-else, так что вы ничего не потеряете. Если вы сомневаетесь, сначала укажите наиболее распространенные случаи в операторе switch.
В лучшем случае оптимизатор может найти лучший способ для генерации кода. Обычные вещи, которые делает компилятор, - это создание бинарного дерева решений (сохраняет сравнения и переходы в среднем случае) или просто создание таблицы переходов (работает без сравнения вообще).
источник
if
-else
цепочки в шаблон мета - программирования и сложности генерацииswitch
case
цепочек, что отображение может стать более важным. (и да, древний комментарий, но сеть навсегда, или, по крайней мере, до следующегоДля особого случая, который вы предоставили в своем примере, самый ясный код:
Очевидно, что это просто перемещает проблему в другую область кода, но теперь у вас есть возможность повторно использовать этот тест. У вас также есть больше вариантов, как решить эту проблему. Вы можете использовать std :: set, например:
Я не утверждаю, что это лучшая реализация requireSpecialEvent, просто это вариант. Вы все еще можете использовать переключатель или цепочку if-else, или таблицу поиска, или некоторую битовую манипуляцию со значением, что угодно. Чем более непонятным становится ваш процесс принятия решений, тем большую ценность вы получите от его использования в изолированной функции.
источник
const std::bitset<MAXERR> specialerror(initializer);
используйте его сif (specialerror[numError]) { fire_special_event(); }
. Если вам нужна проверка границ, тоbitset::test(size_t)
будет сгенерировано исключение для значений за пределами. (bitset::operator[]
не проверяет дальность). cplusplus.com/reference/bitset/bitset/test . Это, вероятно, превзойдет сгенерированную компилятором таблицу переходовswitch
, особенно. в не особом случае, когда это будет одна не занятая ветвь.std::set
, я подумал, что, возможно, это плохой выбор. Оказывается, gcc уже компилирует код OP для немедленного тестирования растрового изображения в 32-битной версии. Godbolt: goo.gl/qjjv0e . GCC 5.2 даже сделает это дляif
версии. Кроме того, более поздние версии gcc будут использовать инструкцию битового тестаbt
вместо сдвига, чтобы поместить1
бит в нужное место и использоватьtest reg, imm32
.Переключатель находится быстрее.
Просто попробуйте, если 30 других значений находятся внутри цикла, и сравните его с одним и тем же кодом, используя переключатель, чтобы увидеть, насколько быстрее работает переключатель.
Теперь у коммутатора есть одна реальная проблема : он должен знать во время компиляции значения внутри каждого случая. Это означает, что следующий код:
не скомпилируется.
Большинство людей будут тогда использовать определения (Aargh!), А другие будут объявлять и определять постоянные переменные в том же модуле компиляции. Например:
Итак, в конце концов, разработчик должен выбрать между «скорость + четкость» и «кодовая связь».
(Не то, чтобы выключатель не мог быть написан так, чтобы он запутался как ад ... Большинство переключателей, которые я сейчас вижу, относятся к этой «запутанной» категории »... Но это другая история ...)
,
источник
Компилятор все равно его оптимизирует - переключайтесь, так как он наиболее читабелен.
источник
gcc
не буду этого делать наверняка (для этого есть веская причина). Clang оптимизирует оба случая в бинарный поиск. Например, посмотрите это .Переключатель, если только для удобства чтения. Гигантские, если заявления труднее поддерживать и труднее читать по моему мнению.
ERROR_01 : // преднамеренный провал
или
(ERROR_01 == numError) ||
Последнее более подвержено ошибкам и требует большего набора текста и форматирования, чем первое.
источник
Код для удобочитаемости. Если вы хотите знать, что работает лучше, используйте профилировщик, так как оптимизации и компиляторы различаются, а проблемы с производительностью редко бывают там, где люди думают, что они есть.
источник
Используйте переключатель, это то, для чего оно и чего ожидают программисты.
Хотя я бы добавил лишние ярлыки кейсов - просто чтобы люди чувствовали себя комфортно, я пытался вспомнить, когда / каковы правила их исключения.
Вы не хотите, чтобы следующий программист, работающий над этим, должен был думать о деталях языка (это может быть через несколько месяцев!)
источник
Компиляторы действительно хороши в оптимизации
switch
. Недавние gcc также хороши для оптимизации множества условий вif
.Я сделал несколько тестов на Godbolt .
Когда
case
значения сгруппированы близко друг к другу, gcc, clang и icc достаточно умны, чтобы использовать растровое изображение, чтобы проверить, является ли значение одним из специальных.например, gcc 5.2 -O3 компилирует в
switch
(и что-if
то очень похожее):Обратите внимание, что растровое изображение является непосредственными данными, поэтому нет потенциальной ошибки при обращении к кешу данных или к таблице переходов.
gcc 4.9.2 -O3 компилирует в
switch
растровое изображение, но делает1U<<errNumber
с mov / shift. Компилируетif
версию в серию веток.Обратите внимание, как он вычитает 1 из
errNumber
(с,lea
чтобы объединить эту операцию с ходом). Это позволяет ему вписать растровое изображение в 32-битную немедленную, избегая 64-битной немедленнойmovabsq
который занимает больше байтов инструкции.Более короткая (в машинном коде) последовательность будет:
(Отказ от использования
jc fire_special_event
вездесущ и является ошибкой компилятора .)rep ret
используется в целях ветвления и следующих условных ветвлениях для старых AMD K8 и K10 (pre-Bulldozer): Что означает «rep ret»? , Без этого предсказание ветвлений не работает так же хорошо на этих устаревших процессорах.bt
(битовый тест) с регистром arg быстрый. Он сочетает в себе работу сдвига влево на 1errNumber
бит и делаетtest
, но все еще имеет задержку в 1 цикл и только один цикл Intel UOP. Он медленен с аргументом памяти из-за его семантики "слишком слишком CISC": с операндом памяти для "битовой строки" адрес проверяемого байта вычисляется на основе другого аргумента (деленного на 8), и isn не ограничивается 1, 2, 4 или 8-байтовым фрагментом, на который указывает операнд памяти.Из таблиц команд Agner Fog команда сдвига с переменным счетом медленнее, чем
bt
на недавнем Intel (2 моп вместо 1, а смещение не делает все остальное, что нужно).источник
Преимущества switch () по сравнению с if case: - 1. Switch гораздо эффективнее по сравнению с if else, потому что каждый случай не зависит от предыдущего, в отличие от случая, когда необходимо проверить отдельное утверждение на наличие условий true или false.
Когда нет. значений для одного выражения, переключение регистра является более гибким, чем в другом случае, потому что в этом случае суждение основывается только на двух значениях, то есть либо true, либо false.
Значения в switch определены пользователем, тогда как значения в случае if else основаны на ограничениях.
В случае ошибки операторы в switch могут быть легко перепроверены и исправлены, что сравнительно трудно проверить в случае оператора if else.
Корпус коммутатора гораздо компактнее, его легче читать и понимать.
источник
ИМО, это прекрасный пример того, для чего был сделан провал переключателя.
источник
Если ваши дела, вероятно, останутся сгруппированными в будущем - если более одного случая соответствуют одному результату - переключение может оказаться проще для чтения и обслуживания.
источник
Они работают одинаково хорошо. Производительность примерно такая же, учитывая современный компилятор.
Я предпочитаю операторы if, а не операторы case, потому что они более читабельны и более гибки - вы можете добавить другие условия, не основанные на числовом равенстве, такие как «|| max <min». Но для простого случая, который вы разместили здесь, это не имеет значения, просто делайте то, что наиболее читабельно для вас.
источник
переключатель определенно предпочтительнее. Проще посмотреть список дел коммутатора и точно знать, что он делает, чем читать длинное условие if.
Дублирование в
if
состоянии тяжело для глаз. Предположим, что один из==
был написан!=
; вы заметили? Или, если один экземпляр 'numError' был написан 'nmuError', который только что скомпилировался?Обычно я предпочитаю использовать полиморфизм вместо переключателя, но без более подробной информации о контексте трудно сказать.
Что касается производительности, лучше всего использовать профилировщик для измерения производительности вашего приложения в условиях, которые похожи на то, что вы ожидаете в дикой природе. В противном случае вы, вероятно, оптимизируете не в том месте и не в том направлении.
источник
Я согласен с гибкостью решения коммутатора, но, по-моему, вы угоняете коммутатор здесь.
Назначение переключателя состоит в том, чтобы иметь различную обработку в зависимости от значения.
Если бы вам пришлось объяснять свой алгоритм в псевдокоде, вы бы использовали if, потому что семантически, вот что это такое: если what_error делает это ...
Так что если вы не собираетесь когда-нибудь изменить свой код, чтобы иметь конкретный код для каждой ошибки Я бы использовал, если .
источник
Я бы выбрал утверждение if для ясности и условности, хотя я уверен, что некоторые не согласятся. В конце концов, вы хотите сделать что-то,
if
какое- то условие верно! Наличие переключателя с одним действием кажется немного ... ненужным.источник
Я бы сказал, используйте переключатель. Таким образом, вам нужно реализовать разные результаты. Ваши десять одинаковых кейсов могут использовать по умолчанию. Если нужно изменить все, что вам нужно, это явно реализовать это изменение, нет необходимости редактировать значение по умолчанию. Также гораздо проще добавлять или удалять дела из SWITCH, чем редактировать IF и ELSEIF.
Может быть, даже проверить ваше состояние (в данном случае numerror) по списку возможностей, возможно, по массиву, чтобы ваш SWITCH даже не использовался до тех пор, пока не будет достигнут определенный результат.
источник
Поскольку у вас есть только 30 кодов ошибок, закодируйте свою собственную таблицу переходов, затем вы сделаете все варианты оптимизации самостоятельно (переход всегда будет самым быстрым), а не надеетесь, что компилятор поступит правильно. Это также делает код очень маленьким (кроме статического объявления таблицы переходов). Это также имеет дополнительное преимущество, заключающееся в том, что с помощью отладчика вы можете изменить поведение во время выполнения, если вам это нужно, просто перелистывая данные таблицы напрямую.
источник
std::bitset<MAXERR> specialerror;
, тогдаif (specialerror[err]) { special_handler(); }
. Это будет быстрее, чем таблица переходов, особенно в не взятом случае.Я не уверен насчет наилучшей практики, но я бы использовал переключатель - и затем ловил намеренный провал через «default»
источник
Эстетически я склоняюсь к такому подходу.
Сделайте данные немного умнее, чтобы мы могли сделать логику немного тупее.
Я понимаю, это выглядит странно. Вот вдохновение (из того, как я это сделал в Python):
источник
break
внешнююcase
(да, второстепенную) грех, но, тем не менее, грех). :)Вероятно, первый оптимизируется компилятором, что объясняет, почему второй цикл медленнее при увеличении числа циклов.
источник
if
сравнениюswitch
с условием завершения цикла в Java.Когда дело доходит до компиляции программы, я не знаю, есть ли разница. Но что касается самой программы и того, чтобы код был максимально простым, я лично думаю, что это зависит от того, что вы хотите сделать. если еще, если еще заявления имеют свои преимущества, которые я считаю:
Позволяет вам тестировать переменную по определенным диапазонам. Вы можете использовать функции (Стандартная библиотека или Личные) в качестве условных выражений.
(пример:
Тем не менее, если еще, если еще заявления могут быть сложными и грязными (несмотря на ваши лучшие попытки) в спешке. Операторы Switch имеют тенденцию быть более ясными, понятными и более легкими для чтения; но может использоваться только для проверки определенных значений (пример:
Я предпочитаю, если - еще, если - еще заявления, но это действительно зависит от вас. Если вы хотите использовать функции в качестве условий, или вы хотите что-то протестировать с диапазоном, массивом или вектором и / или вы не против иметь дело со сложным вложением, я бы рекомендовал использовать блоки If if if else. Если вы хотите проверить на единичные значения или хотите получить чистый и легко читаемый блок, я бы порекомендовал вам использовать блоки case ().
источник
Я не тот человек, что расскажет вам о скорости и использовании памяти, но смотреть на состояние переключателя - чертовски легче понять, чем большой оператор if (особенно через 2-3 месяца)
источник
Я знаю его старое но
Изменение количества циклов сильно меняется:
В то время как если / иначе: 5 мс Переключатель: 1 мс Макс. Циклов: 100000
В то время как если / иначе: 5 мс Переключатель: 3 мс Макс. Петли: 1000000
В то время как если / иначе: 5 мс Переключатель: 14 мс Макс. Петли: 10000000
В то время как если / иначе: 5 мс Переключатель: 149 мс Макс. Циклов: 100000000
(добавьте больше заявлений, если хотите)
источник
if(max) break
Цикл работает в постоянная время независимо от числа петель? Похоже, JIT-компилятор достаточно умен, чтобы оптимизировать цикл доcounter2=max
. А может быть, это медленнее, чем switch, если первый вызов tocurrentTimeMillis
имеет больше накладных расходов, потому что еще не все JIT-скомпилировано? Размещение петель в другом порядке, вероятно, даст другие результаты.