Навіщо використовувати підхід OO замість гігантського оператора “switch”?


59

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

Я, чесно кажучи, не маю аргументів проти цього ... тому що я не знаю, про чорт, про який він говорить.
Він прав?
Він просто розмовляє попкою?
Просто намагаюся тут вчитися.


7
Ви можете переконатися, що він правий, використовуючи щось на зразок .NET Reflector, щоб переглянути код складання та шукати "таблицю стрибків процесора".
FrustratedWithFormsDesigner

5
"Перекладати оператор компілюється в" таблицю стрибків процесора "Так чи не найгірший спосіб диспетчеризації з усіма чисто-віртуальними функціями. Жодна віртуальна функція просто не пов'язана безпосередньо. Чи скинули ви який-небудь код для порівняння?
S.Lott

64
Код слід писати для ЛЮДЕЙ, а не для машин, інакше ми просто зробимо все в зборі.
maple_shaft

8
Якщо він є такою великою кількістю локшини, цитуйте йому Кнут: "Ми повинні забути про малу ефективність, скажімо про 97% часу: передчасна оптимізація - корінь усього зла".
DaveE

12
Технічне обслуговування. Будь-які інші питання, що відповідають одним словом, я можу вам допомогти?
Метт Еллен

Відповіді:


48

Він, мабуть, старий хакер C, і так, він розмовляє з дупи. .Net не є C ++; компілятор .Net продовжує вдосконалюватись, і більшість розумних хак є контрпродуктивними, якщо не сьогодні, то в наступній версії .Net. Невеликі функції є кращими, тому що .Net JIT-s виконує кожну функцію один раз перед її використанням. Отже, якщо деякі випадки ніколи не потрапляють під час програми LifeCycle програми, то при JIT-складанні цих витрат не виникає ніяких витрат. У всякому разі, якщо швидкість не є проблемою, оптимізації не повинно бути. Напишіть для програміста перший, для компілятора другий. Вашого колегу не буде легко переконати, тож я б емпірично довів, що краще організований код насправді швидший. Я б обрав один з найгірших його прикладів, переписав їх краще, а потім переконався, що ваш код швидше. Вишні, якщо потрібно. Потім запустіть його кілька мільйонів разів, профіліруйте і покажіть йому.

EDIT

Білл Вагнер написав:

Пункт 11: Розуміння залучення малих функцій (Ефективне C # Друге видання) Пам'ятайте, що переклад C # коду в машинно виконуваний код - це двоетапний процес. Компілятор C # генерує ІЛ, які постачаються в зборах. Компілятор JIT генерує машинний код для кожного методу (або групи методів, коли задіяно вбудоване), за потреби. Невеликі функції полегшують компілятору JIT амортизацію цих витрат. Невеликі функції також є більш шансовими кандидатами для вставки. Це не лише малість: простіший керуючий потік має значення так само. Менше гілок управління всередині функцій полегшує компілятору JIT реєстрацію змінних. Писати більш чіткий код - це не просто хороша практика; саме так ви створюєте більш ефективний код під час виконання.

EDIT2:

Отже ... очевидно, що оператор перемикання швидше і краще, ніж купу тверджень if / else, тому що одне порівняння є логарифмічним, а інше - лінійним. http://sequence-points.blogspot.com/2007/10/why-is-switch-statement-faster-than-if.html

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

Я досі не впевнений, що твердження перемикача краще.


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

27
@Job, якщо він насправді правий? Справа не в тому, що він помиляється, справа в тому, що він правий і це не має значення .
Рейн Генріхс

2
Навіть якщо він мав рацію приблизно в 100% випадків, він все ще витрачає наш час.
Джеремі

6
Я хочу вивести очі, намагаючись прочитати ту сторінку, яку ви пов’язали.
AttackingHobo

3
Що з ненавистю C ++? Компілятори C ++ теж покращуються, а великі комутатори такі ж погані як у C ++, як у C #, і саме з тієї ж причини. Якщо вас оточують колишні програмісти C ++, які доставляють вам горе, це не тому, що вони програмісти на C ++, це тому, що вони погані програмісти.
Себастьян Редл

39

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

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

Швидкість можна оцінити кількісно. Мало корисної інформації в "підході А швидше, ніж підхід Б". Питання " Наскільки швидше? ".


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

6
-1 для "Передчасна оптимізація - корінь усього зла". Будь ласка, покажіть всю цитату, а не лише одну частину, яка упереджує думку Кнута.
альтернатива

2
@mathepic: Я навмисно не представляв це як цитату. Це речення, як і є, моя особиста думка, хоча, звичайно, не моє творіння. Хоча можна відзначити, що хлопці з c2, здається, вважають саме цю частину основною мудрістю.
back2dos

8
@alternative Повна цитата Кнута "Немає сумнівів, що грааль ефективності призводить до зловживань. Програмісти витрачають величезну кількість часу на роздуми або занепокоєння швидкості некритичних частин своїх програм, і ці спроби ефективності насправді мають сильний негативний вплив при налагодженні та технічному обслуговуванні. Ми повинні забути про невелику ефективність, скажімо, про 97% часу: передчасна оптимізація - корінь усього зла ". Досконало описує колегу ОП. IMHO back2dos добре узагальнив цитату з "передчасна оптимізація - корінь усього зла"
MarkJ

2
@MarkJ 97% часу
альтернатива

27

Кому все одно, чи швидше?

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

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

Крім того, у вас з'явилася повна нездатність ефективно використовувати тест (занадто багато можливих шляхів, не кажучи вже про ймовірний брак інтерфейсів тощо), що робить ваш код ще менш ретельним.

[Що стосується зацікавленої сторони: JITter краще працює на менших методах, тому гігантські заяви про перемикання (і притаманні їм великі методи) завдадуть шкоди вашій швидкості у великих збірках, IIRC.]


1
+ величезний приклад передчасної оптимізації.
ShaneC

Однозначно це.
DeadMG

+1 за "гігантський вимикач комутатора навіть
нелегко

2
Гігантське твердження про перемикання набагато простіше зрозуміти новому хлопцеві: всі можливі способи поведінки зібрані саме там, у приємному списку. Непрямі дзвінки надзвичайно важко прослідкувати, в гіршому випадку (покажчик функції) потрібно шукати всю базу коду для функцій правильної підпису, а віртуальні дзвінки лише трохи кращі (пошук функцій потрібного імені та підпису та пов'язані по спадку). Але ремонтопридатність - це не лише для читання.
Ben Voigt

14

Відійдіть від заяви перемикача ...

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


11
Це поставляється із застереженням. Існують операції (функції / методи) та типи. Коли ви додаєте нову операцію, вам потрібно буде лише змінити код в одному місці для операторів переключення (додати одну нову функцію з оператором switch), але вам доведеться додати цей метод до всіх класів у випадку OO (порушує відкриті / закритий принцип). Якщо ви додаєте нові типи, вам потрібно торкнутися кожного оператора перемикання, але у випадку OO ви просто додасте ще один клас. Тому для прийняття обґрунтованого рішення ви повинні знати, чи будете ви додавати більше операцій до існуючих типів або додавати більше типів.
Скотт Вітлок

3
Якщо вам потрібно додати більше операцій до існуючих типів у парадигмі OO, не порушуючи OCP, то я вважаю, що для цього потрібна схема відвідувачів.
Скотт Вітлок

3
@Martin - зателефонуйте, якщо захочете, але це добре відомий компроміс. Я посилаю вас на Clean Code від RC Martin. Він переглядає свою статтю про OCP, пояснюючи те, що я окреслив вище. Ви не можете одночасно проектувати всі майбутні вимоги. Ви повинні зробити вибір між тим, чи більше шансів додати більше операцій чи більше типів. ОО підтримує додавання типів. Ви можете використовувати OO, щоб додати більше операцій, якщо ви будете моделювати операції як класи, але це, здається, потрапляє у шаблон відвідувача, який має свої проблеми (особливо, накладні витрати).
Скотт Уїтлок

8
@Martin: Ви коли-небудь писали парсер? Досить поширеними є великі корпуси комутаторів, які включають наступний маркер у буфері lookahead. Ви можете замінити ці комутатори віртуальними викликами функцій на наступний маркер, але це був би кошмар підтримки. Це рідко, але іноді корпус комутатора насправді є кращим вибором, оскільки він зберігає код, який слід читати / змінювати разом у безпосередній близькості.
nikie

1
@Martin: Ви використовували такі слова, як "ніколи", "ніколи" та "Поппікок", тому я припускав, що ви говорите про всі випадки без винятку, а не лише про найпоширеніший випадок. (І BTW: Люди все ще пишуть парсери вручну. Наприклад, CPython парсер все ще пишеться від руки, IIRC.)
nikie

8

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

Показники, про які вам потрібно піклуватися:

  • Швидкість внесення змін
  • Швидкість пошуку проблеми, коли вона трапляється

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

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

Що не так з масовими операторами переключення для мови ОО?

  • Програмний потік управління відбирається від об'єкта, де він належить, і розміщується поза об'єктом
  • Багато пунктів зовнішнього контролю перекладаються на багато місць, які потрібно переглянути
  • Незрозуміло, де зберігається стан, особливо якщо перемикач знаходиться в циклі
  • Найшвидше порівняння - це зовсім не порівняння (ви можете уникнути необхідності в багатьох порівняннях з хорошим об'єктно-орієнтованим дизайном)
  • Ефективніше перебирати ваші об’єкти і завжди викликати один і той же метод на всіх об'єктах, ніж це змінювати ваш код на основі типу об'єкта або enum, що кодує тип.

8

Я не купую аргумент ефективності; вся справа в технічному обслуговуванні коду.

АЛЕ: іноді гігантський оператор перемикання легше підтримувати (менше коду), ніж купу малих класів, що перекривають віртуальну функцію (а) абстрактного базового класу. Наприклад, якби ви реалізували емулятор процесора, ви б не реалізували функціональність кожної інструкції в окремому класі - ви просто вкладете її у гігантський штрих на опкоді, можливо, викликаючи допоміжні функції для отримання більш складних інструкцій.

Правило великого пальця: якщо комутатор якимось чином виконується на ТИПІ, ви, ймовірно, повинні використовувати успадкування та віртуальні функції. Якщо комутатор виконується на значеннях фіксованого типу (наприклад, опкод інструкції, як зазначено вище), добре залишити його таким, яким він є.


5

Ви не можете переконати мене в цьому:

void action1()
{}

void action2()
{}

void action3()
{}

void action4()
{}

void doAction(int action)
{
    switch(action)
    {
        case 1: action1();break;
        case 2: action2();break;
        case 3: action3();break;
        case 4: action4();break;
    }
}

Значно швидше, ніж:

struct IAction
{
    virtual ~IAction() {}
    virtual void action() = 0;
}

struct Action1: public IAction
{
    virtual void action()    { }
}

struct Action2: public IAction
{
    virtual void action()    { }
}

struct Action3: public IAction
{
    virtual void action()    { }
}

struct Action4: public IAction
{
    virtual void action()    { }
}

void doAction(IAction& actionObject)
{
    actionObject.action();
}

Крім того, версія OO є просто більш доступною.


8
Щодо деяких речей і для менших обсягів дій, версія OO є набагато густішою. Він повинен мати якусь фабрику, щоб перетворити якусь цінність у створення IAction. У багатьох випадках набагато легше читати просто замість цього значення.
Зан Лінкс

@Zan Lynx: Ваш аргумент занадто загальний. Створення об'єкта IAction настільки ж складно, як і отримати ціле число дії, ні складніше, ні простіше. Таким чином, ми можемо вести справжню розмову, не може бути загальним. Розглянемо калькулятор. Яка тут різниця у складності? Відповідь нульова. Як усі дії попередньо створені. Ви отримуєте дані від користувача та його вже дію.
Мартін Йорк

3
@Martin: Ви передбачаєте додаток для калькулятора GUI. Візьмемо замість програми для калькулятора клавіатури, написану для C ++ у вбудованій системі. Тепер у вас є ціле число скан-коду з апаратного реєстру. Тепер що менш складне?
Зан Лінкс

2
@Martin: Ви не бачите, як ціла -> таблиця пошуку -> створення нового об'єкта -> віртуальна функція виклику складніша, ніж ціла -> перемикач -> функція? Як ти цього не бачиш?
Зан Лінкс

2
@Martin: Можливо, буду. Тим часом поясніть, як ви отримуєте об'єкт IAction для виклику action () з цілого числа без таблиці пошуку.
Зан Лінкс

4

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

ТАКОЖ : Майже напевно випадок, що вашій конкретній програмі не потрібно турбуватися про подібну мікрооптимізацію, або ви б не використовували .net в першу чергу. Якщо не вистачає дуже обмежених вбудованих додатків або інтенсивної роботи процесора, ви завжди повинні дозволити компілятору займатися оптимізацією. Концентруйтеся на написанні чистого, ремонтованого коду. Це майже завжди має велике значення, ніж кілька десятих частин наносекунди за часом виконання.


3

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


3

оператор переключення в коді OOP - це сильна індикація відсутніх класів

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


3

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

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

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

Він прав?

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

Більш корисно не думати про цю вартість з точки зору таблиць стрибків, а більше з точки зору бар'єру оптимізації. Для поліморфізму виклик Base.method()не дозволяє компілятору знати, яка функція насправді буде викликана, якщо methodвона віртуальна, не запечатана та може бути замінена. Оскільки він не знає, яку функцію насправді буде викликати заздалегідь, він не може оптимізувати виклик функції та використовувати більше інформації для прийняття оптимізаційних рішень, оскільки насправді не знає, яку функцію буде викликано на час складання коду.

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

Він просто розмовляє попкою?

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

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

Пенні важливі, коли у вас є дешева функція, яка не робить стільки роботи, яка називається мільярд разів у дуже тісному критичному циклі. У цьому випадку ми в кінцевому підсумку заощаджуємо 10 мільйонів доларів. Не варто голити копійки, коли у вас є функція, яка називається два рази, за яку організм один коштує тисячі доларів. Не розумно витрачати свій час, торгуючись за копійки під час придбання автомобіля. Варто торгуватися за копійки, якщо ви купуєте мільйон банок соди у виробника. Запорукою ефективної оптимізації є розуміння цих витрат у відповідному контексті. Хтось, хто намагається зекономити копійки на кожній покупці, і припускає, що всі інші намагаються торгуватися за копійки незалежно від того, що вони купують, не є кваліфікованим оптимізатором.


2

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


1
Поряд із тестами на встановлення часу, дамп коду може допомогти показати, як працює диспетчеризація методу C ++ (та C #).
С.Лотт

2

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

Напр. якщо ви переходите між Dog, Catа Elephant, а іноді Dogі Catмаєте однаковий випадок, ви можете змусити їх успадковувати абстрактний клас DomesticAnimalі помістити ці функції в абстрактний клас.

Крім того, я був здивований, що кілька людей використали аналізатор як приклад того, де ти не використовуєш поліморфізм. Для дерев’яного аналізатора це, безумовно, неправильний підхід, але якщо у вас є щось на кшталт збірки, де кожен рядок є дещо незалежним, і почніть з опкоду, який вказує, як слід інтерпретувати решту рядка, я б цілком використовував поліморфізм і Фабрика. Кожен клас може реалізовувати такі функції, як ExtractConstantsабо ExtractSymbols. Я використовував такий підхід для іграшкового перекладача BASIC.


Перемикач може успадковувати поведінку також через випадок за замовчуванням. "... розширює BaseOperationVisitor" стає "за замовчуванням: BaseOperation (вузол)"
Samuel Danielson

0

"Ми повинні забути про малу ефективність, скажімо, про 97% часу: передчасна оптимізація - корінь усього зла"

Дональд Кнут


0

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


1
Відсутнє "не"? У C # не всі функціональні дзвінки є віртуальними. C # не є Java.
Ben Voigt

0

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

Компілятор C # перетворює оператори перемикання лише з кількох випадків у ряд if / else, тому не швидше, ніж використання if / else. Компілятор перетворює великі оператори переключення у словник (таблиця стрибків, на яку посилається ваш колега). Щоб отримати докладнішу інформацію, дивіться цю відповідь на питання про переповнення стека у цій темі .

Велике твердження про перемикання важко читати та підтримувати. Словник «справ» та функцій набагато простіше читати. Оскільки саме так перетворюється перемикач, вам і вашому колезі було б радимо використовувати словники безпосередньо.


0

Він не обов'язково розмовляє з дупи. Принаймні, у C і C ++ switchвисловлюваннях можна оптимізувати для переходів таблиць, поки я ніколи не бачив, щоб це відбувалося з динамічною відправленням у функції, яка має доступ лише до базового вказівника. Принаймні, для останнього потрібен значно розумніший оптимізатор, дивлячись на набагато більше оточуючий код, щоб зрозуміти, який саме підтип використовується з виклику віртуальної функції через базовий вказівник / посилання.

Крім того, динамічна відправка часто служить "бар'єром для оптимізації", тобто компілятор часто не зможе вбудувати код і оптимально розподілити регістри, щоб мінімізувати розсип стека та все, що фантазії, тому що він не може зрозуміти, що віртуальну функцію буде викликано через базовий покажчик, щоб вбудувати її та виконати всі її магії оптимізації. Я не впевнений, що ви навіть хочете, щоб оптимізатор був таким розумним і намагався оптимізувати виклики непрямих функцій, оскільки це потенційно може призвести до того, що багато гілок коду потрібно генерувати окремо внизу заданого стеку викликів (функція, яка foo->f()матиме дзвінки щоб генерувати абсолютно інший машинний код від того, який дзвонитьbar->f() через базовий вказівник, і функція, яка викликає цю функцію, повинна була б генерувати дві або більше версій коду тощо - кількість машинного коду, що генерується, була б вибухонебезпечною - можливо, не так вже й погано зі слідом JIT, який генерує код під час руху під час простеження гарячих контурів виконання).

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

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

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

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