Чи є якась істотна різниця між використанням if / else та регістром перемикання в C #?


219

Яка вигода / мінус у використанні switchоператора порівняно із знаком if/elseу C #. Я не можу уявити, що існує така велика різниця, крім, мабуть, вигляду вашого коду.

Чи є якась причина, чому результуюча ІЛ або пов'язана з цим продуктивність виконання кардинально відрізнятиметься?

Пов’язано: Що швидше, увімкніть рядок або інший тип?



3
Це питання цікаве теоретично лише для більшості розробників, якщо ви часто не повторяєтеся в один мільярд разів. (Тоді скористайтеся оператором перемикання і перейдіть від 48 до 43 секунд ...) Або словами Дональда Кнута: "Ми повинні забути про невелику ефективність, скажімо про 97% часу: передчасна оптимізація - корінь усього зла" en.wikipedia.org/wiki/Program_optimization#When_to_optimize
Сир

Я часто використовую if / else замість комутатора через невмілий спільний діапазон комутатора.
Джош

Відповіді:


341

Оператор SWITCH виробляє лише таку ж збірку, що і IF в режимі налагодження або сумісності. У випуску вона буде складена у таблицю стрибків (через оператор MSIL 'switch') - що є O (1).

C # (на відміну від багатьох інших мов) також дозволяє вмикати струнні константи - і це працює трохи інакше. Очевидно, що не можна будувати таблиці стрибків для рядків довільної довжини, тому найчастіше такий комутатор буде зібраний у стек IF.

Але якщо кількість умов достатньо велика для покриття накладних витрат, компілятор C # створить об'єкт HashTable, заповнить його рядковими константами і зробить пошук на цій таблиці з подальшим стрибком. Пошук хешбета не є строго O (1) і має помітні постійні витрати, але якщо кількість міток регістру велика, це буде значно швидше, ніж порівняння з кожною рядковою константою в ІФ.

Підсумовуючи це, якщо кількість умов більше 5 або більше, віддайте перевагу SWITCH над IF, інакше використовуйте все, що виглядає краще.


1
Ви впевнені, що компілятор C # створить хеш-таблицю? Я говорив про хеш-таблицю в нашому обговоренні коментарів вище, стосувався власних компіляторів, а не компілятора C #. Який поріг використовує компілятор C # для створення хеш-таблиці?
Скотт Вішневський

8
Я думаю, десь десять. 20 бути на безпечній стороні. І btw, мій гнів не на вас, а на людей, які підтримують і приймають.
има

48
Трохи експериментів пропонує підрахунок <= 6: "якщо"; count> = 7: словник. Це з компілятором MS .NET 3.5 C # - він, звичайно, може змінюватися між версіями та постачальниками.
Джон Скіт

37
Гей, я тобі зобов'язаний. Вибачте за те, що є кістковою головою.
Скотт Вішневський

Надалі, для практичних застосувань, чи є різниця у реальному світі більшість часу? Я вважаю, що заяви C Switch в C # є незвичайними, їх синтаксис не зовсім схожий ні на що інше, і я вважаю, що вони роблять мій код менш читабельним, чи варто перешкоджати використанню операторів перемикань, або я просто програмую з else ifs і тільки приходжу назад і замінити їх, якщо я потрапив у вузькі місця продуктивності?
Майстер Джейсона

54

Загалом (з огляду на всі мови та всі компілятори) оператор перемикача CAN SOMETIMES може бути більш ефективним, ніж оператор if / else, оскільки компілятору легко генерувати таблиці стрибків з операторів перемикання. Можна зробити те ж саме і для операторів if / else з відповідними обмеженнями, але це набагато складніше.

У випадку з C # це теж правда, але з інших причин.

З великою кількістю рядків є значна перевага в продуктивності використання оператора switch, оскільки компілятор буде використовувати хеш-таблицю для здійснення стрибка.

З невеликою кількістю струн продуктивність між ними однакова.

Це тому, що в такому випадку компілятор C # не генерує таблицю стрибків. Натомість він генерує MSIL, що еквівалентно блокам IF / ELSE.

Існує інструкція MSIL "твердження про перемикання", що при натисканні буде використовувати таблицю стрибків для реалізації оператора перемикання. Однак він працює лише з цілими типами (це питання задає рядки).

Для невеликої кількості рядків компілятору ефективніше генерувати блоки IF / ELSE, тож використовувати хеш-таблицю.

Коли я спочатку помітив це, я зробив припущення, що оскільки блоки IF / ELSE використовуються з невеликою кількістю рядків, компілятор здійснив те саме перетворення для великої кількості рядків.

Це було НЕПРАВНО. "IMA" був досить люб'язний, щоб вказати на це мені (ну ... він був недобрий до цього, але він мав рацію, і я помилявся, що важлива частина)

Я також висловив припущення про відсутність інструкції "перемикання" в MSIL (я зрозумів, якщо там був примітив комутатора, чому вони не використовували його з хеш-таблицею, тому не повинно бути примітиву перемикача. ...). Це було як неправильно, так і неймовірно нерозумно з мого боку. Знову "IMA" вказав на це мені.

Я зробив тут оновлення, оскільки це найвищий рейтинг і прийнята відповідь.

Однак я зробив це Community Wiki, тому що вважаю, що я не заслуговую на те, щоб REP не помилявся. Якщо ви отримаєте шанс, будь ласка, проголосуйте за повідомлення 'ima'.


3
У MSIL є перемикач примітиву, і c # заяви компілюються в загально C-подібний пошук. За певних обставин (цільова платформа, клавіші перемикання тощо) перемикач може бути розширений на ІФ під час компіляції, але це лише міра резервної сумісності.
има

6
Все, що я можу зробити, - це вибачення за дурну помилку. Повірте, я відчуваю себе тупою. Якщо серйозно, я думаю, що це все-таки найкраща відповідь. У власних компіляторах можна використовувати хеш-таблицю для здійснення стрибка, так що це не якась жахливо неправильна річ. Я допустив одну помилку.
Скотт Вісневський

9
Ima, якщо є помилки, вкажіть їх. Звучить, що Скотт буде радий виправити повідомлення. Якщо ні, інші, хто здобув можливість виправити відповідь, зроблять це. Це єдиний спосіб, як працює такий сайт, і, здається, загалом він працює. Або візьміть свій м’яч та йдіть додому :)
jwalkerjr

2
@Scott: Я б закликав вас відредагувати другий та третій абзаци, щоб чітко вказати "для рядків". Люди можуть не читати оновлення внизу.
Джон Скіт

4
@ima Якщо ви вважаєте, що відповідь об'єктивно неправильна, відредагуйте її, щоб вона була правильною. Ось чому кожен може редагувати відповіді.
Майлз Рут

18

Три причини віддати перевагу switch:

  • Компілятор, націлений на власний код, часто може компілювати оператор перемикання в одну умовну гілку плюс непрямий стрибок, тоді як послідовність ifs вимагає послідовності умовних гілок . Залежно від щільності випадків було написано багато наукових праць про те, як ефективно складати виписки з справ; деякі з них пов’язані зі сторінки компілятора lcc . (У Lcc був один з більш інноваційних компіляторів для комутаторів.)

  • Оператор перемикання - це вибір між взаємовиключними альтернативами, і синтаксис комутатора робить цей потік управління більш прозорим для програміста, ніж гніздо операторів if-then-else.

  • У деяких мовах, включаючи, безумовно, ML та Haskell, компілятор перевіряє, чи не залишили ви жодних справ . Я розглядаю цю особливість як одну з головних переваг ML та Haskell. Я не знаю, чи може це зробити C #.

Анекдот: на лекції, яку він читав, коли отримував нагороду за все життя, я чув, як Тоні Хоар говорив, що з усіх речей, які він робив у своїй кар'єрі, було три, якими він найбільше пишався:

  • Винахідництво Quicksort
  • Винайдення оператора перемикання (який Тоні назвав caseоператором)
  • Починаючи і закінчуючи свою кар’єру в галузі

Я не уявляю, як житиswitch .


16

Компілятор майже все оптимізує в той же код з незначними відмінностями (Knuth, хтось?).

Різниця полягає в тому, що оператор переключення чистіший, ніж п’ятнадцять, якщо інші операції з'єднані разом.

Друзі не дозволяють друзям складати заяви, якщо інше.


13
"Друзі не дозволяють друзям складати висловлювання". Ви повинні зробити симпатичного сидіння :)
Меттью М. Осборн

14

Насправді оператор перемикання є більш ефективним. Компілятор оптимізує його до таблиці пошуку, де з операторами if / else він не може. Суть в тому, що оператор перемикання не може використовуватися зі змінними значеннями.
Ви не можете:

switch(variable)
{
   case someVariable
   break;
   default:
   break;
}

це має бути

switch(variable)
{
  case CONSTANT_VALUE;
  break;
  default:
  break;
}

1
у вас є якісь цифри? Мені цікаво, наскільки добре компілятор міг оптимізувати швидку операцію для If / Else
Matthew M. Osborn

так, я вважаю, що оператор switch завжди оптимізується до O (1), де оператором if if буде O (n), де n - позиція правильного значення в операторах if / else if.
kemiller2002

У випадку C # це неправда, для отримання додаткової інформації див. Мою публікацію нижче.
Скотт Вішневський

Я в цьому зовсім не впевнений, але я не можу знайти інформацію в книзі, клянусь, що знайшов її. Ви впевнені, що не дивитеся на код MSIL без оптимізації. Він не створить таблицю стрибків, якщо ви не складете з оптимізацією.
kemiller2002

Я компілюється як в режимі налагодження, так і в роздрібній торгівлі, і в обох випадках він генерує блоки if / else. Ви впевнені, що книга, яку ви дивилися, говорила про C #? Ця книга, мабуть, була чи то укладачем, чи то книгою про C чи C ++
Скотт Вішневський

14

Я не бачив, щоб хто-небудь інший піднімав (очевидно?) Пункт про те, що передбачувана перевага ефективності оператора перемикання залежить від того, що різні випадки є приблизно однаковою. У випадках, коли одне (або декілька) значень набагато вірогідніше, сходова клітка може бути набагато швидшою, забезпечивши перевірку найбільш поширених випадків:

Так, наприклад:

if (x==0) then {
  // do one thing
} else if (x==1) {
  // do the other thing
} else if (x==2) {
  // do the third thing
}

проти

switch(x) {
  case 0: 
         // do one thing
         break;
  case 1: 
         // do the other thing
         break;
  case 2: 
         // do the third thing
         break;
}

Якщо x дорівнює 90% часу, код "if-else" може бути вдвічі швидшим, ніж код на основі комутатора. Навіть якщо компілятор перетворить "перемикач" на якусь розумну керовану таблицю goto, він все одно не буде настільки швидким, як проста перевірка нуля.


3
Ніякої передчасної оптимізації! Загалом, якщо у вас є більше ніж декілька випадків, і вони є switchнесумісними, switchтвердження краще (більш читабельне, іноді швидше). Якщо ви знаєте, що один випадок набагато імовірніший, ви можете витягнути це, щоб утворити if- else- switchконструкцію, і якщо це помірно швидше , ви залишаєте це. (Повторіть, якщо потрібно). Якщо switchвироджене і стає занадто малим, заміщення регулярних виразів зробить більшу частину роботи з перетворення його на- else ifланцюг.
ніхто

6
Оригінальне запитання (три роки тому!) Просто задало перевагу та недоліки між функцією if / else та перемиканням. Це один приклад. Я особисто бачив, як така оптимізація суттєво впливає на час роботи.
Марк Бессі

7

часто це буде виглядати краще - тобто буде легше зрозуміти, що відбувається. Зважаючи на те, що вигода від продуктивності буде в кращому випадку мінімальною, перегляд коду є найважливішою відмінністю.

Отже, якщо якщо / else виглядає краще, використовуйте його, інакше використовуйте оператор перемикання.


4

Побічна тема, але я часто хвилююсь (і частіше бачу) if/ elseі switchтвердження стає занадто великим із занадто великою кількістю справ. Вони часто шкодять ремонту.

До поширених винуватців належать:

  1. Занадто багато всередині кількох, якщо висловлювань
  2. Більше тверджень про випадки, ніж по-людськи, можливо проаналізувати
  3. Занадто багато умов в оцінці if, щоб знати, що шукають

Виправити:

  1. Екстракт до методу рефакторингу.
  2. Використовуйте словник з покажчиками методів замість регістру або використовуйте IoC для додаткової налаштування. Фабрики методів також можуть бути корисними.
  3. Витягніть умови власним методом

4

Відповідно до цього посилання, ІФ порівняно з переключенням тестування ітерації за допомогою перемикача, і якщо оператор, як для 1 000 000 000 ітерацій, час, взятий за допомогою вимикача = 43,0 с і за умови виписки = 48,0 с

Це буквально 20833333 ітерації в секунду. Отже, чи нам справді потрібно більше зосередитися,

PS: Просто знати різницю продуктивності для невеликого переліку умов.


Це цвяхи для мене.
Грег Гем

3

Якщо ви просто використовуєте, якщо або інше твердження, базове рішення використовує порівняння? оператор

(value == value1) ? (type1)do this : (type1)or do this;

Ви можете виконати або рутину в комутаторі

switch(typeCode)
{
   case TypeCode:Int32:
   case TypeCode.Int64:
     //dosomething here
     break;
   default: return;
}

2

Це насправді не відповідає вашому запитанню, але враховуючи, що між компільованими версіями буде незначна різниця, я б закликав вас написати свій код таким чином, який найкраще описує ваші наміри. Не тільки є більший шанс компілятора зробити те, що ви очікуєте, але це полегшить збереження вашого коду для інших.

Якщо ваш намір полягає в розгалуженні вашої програми на основі значення однієї змінної / атрибута, тоді оператор перемикання найкраще представляє цей намір.

Якщо ваш намір полягає у розгалуженні вашої програми на основі різних змінних / атрибутів / умов, тоді це завдання if / else, якщо ланцюжок найкраще представляє цей намір.

Я підтверджую, що коді є правильним щодо того, що люди забувають команду break, але майже так само часто я бачу людей, які роблять складні випадки, коли блоки, де вони помиляються {}, значить, рядки, які мають бути в умовному заяві, не є. Це одна з причин, коли я завжди включаю {} у свої заяви if, навіть якщо в ньому є один рядок. Це не тільки легше читати, але якщо мені потрібно додати ще один рядок у умовному, я не можу забути додати його.


2

Питання інтересу. Це з’явилося кілька тижнів тому на роботі, і ми знайшли відповідь, написавши фрагмент прикладу та переглянувши його у .NET Reflector (рефлектор - це приголомшливо! Мені це подобається).

Це те, що ми виявили: Дійсний оператор переключення для будь-якого іншого, крім рядка, збирається в IL як оператор перемикання. Однак якщо це рядок, вона переписується як if / else if / else в IL. Тож у нашому випадку ми хотіли знати, як оператори перемикання порівнюють рядки, наприклад, залежно від регістру тощо, і рефлектор швидко дав нам відповідь. Це було корисно знати.

Якщо ви хочете зробити порівняння з великим регістром на рядках, тоді ви можете використовувати оператор перемикання, оскільки це швидше, ніж виконувати String.Compare в if / else. (Редагувати: Прочитайте, що швидше, увімкніть рядок або накресліть на тип? Для деяких фактичних тестів на ефективність) Однак якщо ви хочете зробити нечутливі до регістру, тоді краще використовувати if / else, оскільки отриманий код не дуже гарний.

switch (myString.ToLower())
{
  // not a good solution
}

Найкраще правило - використовувати заяви переключення, якщо це має сенс (серйозно), наприклад:

  • це покращує читабельність вашого коду
  • ви порівнюєте діапазон значень (float, int) або enum

Якщо вам потрібно маніпулювати значенням, яке подається в оператор переключення (створити тимчасову змінну, на яку слід переключитися), вам, ймовірно, слід використовувати оператор управління if / else.

Оновлення:

Насправді краще перетворити рядок у великі регістри (наприклад, ToUpper()тому що, мабуть, є додаткові оптимізації, які може зробити компілятор щойно вчасно, порівняно з ToLower(). Це мікрооптимізація, проте в тісному циклі це може бути корисним.


Маленька бічна примітка:

Для покращення читабельності операторів переключення спробуйте наступне:

  • поставте першу вірогідну гілку, тобто найбільш доступну
  • якщо вони, швидше за все, трапляються, перерахуйте їх в алфавітному порядку, щоб їх було легше знайти.
  • ніколи не використовуйте за замовчуванням все останнє, що залишилося, це ліниво і викличе проблеми пізніше в житті коду.
  • використовуйте за замовчуванням все для встановлення невідомого стану, хоча це малоймовірно, що воно коли-небудь виникне. саме це стверджує, що добре.

У багатьох випадках використання ToLower () є правильним рішенням, особливо якщо випадків багато, і хеш-таблиця генерується.
Blaisorblade

"Якщо вам потрібно маніпулювати значенням для подачі у оператор переключення (створити тимчасову змінну, на яку слід переключитися), ви, ймовірно, повинні використовувати оператор управління if / else." - Гарна порада, дякую.
Підступність

2

Оператор перемикання, безумовно, швидше, ніж якщо, якщо ще. Є найшвидші, які були на ньому поставлені BlackWasp

http://www.blackwasp.co.uk/SpeedTestIfElseSwitch.aspx

--Перевір

Але сильно залежить від можливостей, які ви намагаєтеся врахувати, але я намагаюся використовувати оператор перемикання, коли це можливо.


1

Думаю, не лише на C #, а й на всіх мовах на основі С: оскільки перемикач обмежений константами, можливо створити дуже ефективний код за допомогою "таблиці перестрибування". Випадок C - це справді хороший старий FORTRAN, який обчислюється GOTO, але випадок C # все ще перевіряється на постійну.

Адже оптимізатор не зможе зробити той самий код. Розглянемо, наприклад,

if(a == 3){ //...
} else if (a == 5 || a == 7){ //...
} else {//...
}

Оскільки це складові булеви, згенерований код повинен обчислити значення та короткий цикл. Тепер розглянемо еквівалент

switch(a){
   case 3: // ...
    break;
   case 5:
   case 7: //...
    break;
   default: //...
}

Це можна скласти в

BTABL: *
B3:   addr of 3 code
B5:
B7:   addr of 5,7 code
      load 0,1 ino reg X based on value
      jump indirect through BTABL+x

тому що ви неявно говорите компілятору, що йому не потрібно обчислювати АБО і тести рівності.


Немає жодної причини, щоб хороший оптимізатор не міг обробляти 1-й код, доки оптимізатор реалізований. "Компілятор не може оптимізувати" просто залежить від смислових відмінностей, які може примирити лише людина (тобто, якщо викликається f (), він не знає, що f () завжди повертає 0 або 1).
Blaisorblade

0

Мій професор-професор запропонував не перемикати заяви, оскільки люди часто забували перерву або використовували її неправильно. Я не можу пригадати саме те, що він сказав, але щось подібне до того, що, дивлячись на якусь базову кодову інформацію, яка показала приклади заяви про перемикання (років тому), було також багато помилок.


Це насправді не проблема в C #. Дивіться: stackoverflow.com/questions/174155/… ... а також читайте stackoverflow.com/questions/188461/… для обговорення того, чому життя в страху може бути не найкращою політикою ...
Shog9

0

Щось що я помітив, це те, що ви можете комбінувати if / else та переключати заяви! Дуже корисно, коли потрібно перевірити передумови.

if (string.IsNullOrEmpty(line))
{
    //skip empty lines
}
else switch (line.Substring(0,1))
{
    case "1":
        Console.WriteLine(line);
        break;
    case "9":
        Console.WriteLine(line);
        break;
    default:
        break;
}

3
Я знаю, що це по-старому, але я думаю, що технічно ти нічого не "поєднуєш". Кожен раз, коли у вас є "ще" без фігурних дужок, наступне твердження буде виконано. Цей вислів може бути однорядним твердженням, як правило, показаним з відступом у наступному рядку, або складеними висловлюваннями, такими як у випадку, якщо, перемикання, використання, блокування тощо. Іншими словами, ви можете мати "else if", " Ще комутатор "," ще використовую "і т. д. Сказавши це, мені подобається, як це виглядає і майже виглядає навмисно. (Відмова: Я не перепробував все це, тому я можу помилятися!)
Нельсон Ротермель

Нельсоне, ти на 100% правильний. Я зрозумів, чому це відбувається після того, як я опублікував цю відповідь.
Навіть Мієн

0

Я думаю, що перемикання швидше, ніж якщо умови, наприклад, дивіться, чи є така програма, як:

Напишіть програму, щоб ввести будь-яке число (від 1 до 99) і перевірте, в якому слоті а) 1 - 9, потім слот один б) 11 - 19, потім слот два в) 21-29, потім слот три і так далі до 89- 99

Тоді увімкніть, якщо вам доведеться створити багато умов, але випадок перемикання сина ви повинні просто набрати

Вимикач (немає / 10)

а у випадку 0 = 1-9, випадок 1 = 11-19 тощо

це буде так просто

Є ще багато таких прикладів!


0

заява перемикання в основному є порівнянням для рівності. У події клавіатури є велика перевага перед оператором переключення, коли легко писати та читати код, тоді, якщо заява ififf не буде, відсутність {кронштейна} також може викликати занепокоєння.

char abc;
switch(abc)
{
case a: break;
case b: break;
case c: break;
case d: break;
}

Заява if elseif чудово підходить для більш ніж одного рішення, якщо (theAmountOfApples більше, ніж 5 && theAmountOfApples менше 10), збережіть ваші яблука ще, якщо (theAmountOfApples більше 10 || theAmountOfApples == 100) продаєте ваші яблука. Я не пишу c # або c ++, але я навчився цього, перш ніж я дізнався Java, і вони є близькими мовами.


0

Один з можливих недоліків операторів переключення - це відсутність кількох умов. Ви можете мати кілька умов для операторів if (else), але не декілька випадків з різними умовами в комутаторі.

Оператори перемикання не підходять для логічних операцій, що виходять за межі простих булевих рівнянь / виразів. Для цих булевих рівнянь / виразів це виразно підходить, але не для інших логічних операцій.

У вас набагато більше свободи з логікою, доступною в операторах If, але читабельність може постраждати, якщо оператор If стає непростим або обробляється неякісно.

Обидва мають місце в залежності від контексту того, з чим стикаєтесь.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.