Зачем использовать ОО-подход вместо гигантского «переключателя»?

59

Я работаю в .Net, C # shop, и у меня есть коллега, который настаивает на том, чтобы мы использовали гигантские операторы Switch в нашем коде с большим количеством «падежей», а не с более объектно-ориентированными подходами. Его аргумент последовательно восходит к тому факту, что оператор Switch компилируется в «таблицу переходов процессора» и, следовательно, является самым быстрым вариантом (хотя в других случаях нашей команде говорят, что нас не волнует скорость).

У меня, честно говоря, нет аргументов против этого ... потому что я не знаю, какого черта он говорит.
Он прав?
Он просто говорит свою задницу?
Просто пытаюсь учиться здесь.

Джеймс П. Райт
источник
7
Вы можете проверить, прав ли он, используя что-то вроде .NET Reflector, чтобы посмотреть код сборки и найти «таблицу переходов процессора».
FrustratedWithFormsDesigner
5
«Оператор Switch компилируется в« таблицу переходов ЦП ». Как и в худшем случае, диспетчеризация метода со всеми чисто виртуальными функциями. Ни одна виртуальная функция просто не связана напрямую. Вы сбросили какой-либо код для сравнения?»
S.Lott
64
Код должен быть написан для ЛЮДЕЙ, а не для машин, иначе мы просто сделаем все в сборке.
maple_shaft
8
Если он такой большой лапшой, процитируйте ему слова Кнута: «Мы должны забыть о малой эффективности, скажем, в 97% случаев: преждевременная оптимизация - корень всего зла».
Дейв
12
Ремонтопригодность. Любые другие вопросы с одним словом ответы, которые я могу помочь вам?
Мэтт Эллен

Ответы:

48

Он, вероятно, старый хакер C и да, он говорит из своей задницы. .Net - это не C ++; компилятор .Net продолжает совершенствоваться, и большинство умных взломов приводят к обратным результатам, если не сегодня, то в следующей версии .Net. Небольшие функции предпочтительнее, потому что .Net JIT-ы каждую функцию один раз перед ее использованием. Таким образом, если некоторые случаи никогда не будут затронуты во время жизненного цикла программы, то при их компиляции JIT не будет никаких затрат. Во всяком случае, если скорость не является проблемой, не должно быть оптимизаций. Сначала напишу для программиста, потом для компилятора. Ваш коллега не будет легко убедить, поэтому я бы эмпирически доказал, что лучше организованный код на самом деле быстрее. Я выбрал бы один из его худших примеров, переписал бы их лучше, а затем убедился, что ваш код работает быстрее. Вишня, если нужно. Затем запустите его несколько миллионов раз, профилируйте и покажите ему.

РЕДАКТИРОВАТЬ

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

Пункт 11: Понимание привлекательности небольших функций (эффективное издание C #, второе издание) Помните, что перевод кода C # в машинно-исполняемый код - это двухэтапный процесс. Компилятор C # генерирует IL, который доставляется в сборках. При необходимости JIT-компилятор генерирует машинный код для каждого метода (или группы методов, если используется встраивание). Небольшие функции значительно упрощают амортизацию JIT-компилятором. Небольшие функции также с большей вероятностью будут кандидатами на встраивание. Это не просто малость: более простой поток управления имеет такое же значение. Меньшее количество ветвей управления внутри функций облегчает JIT-компилятору регистрацию переменных. Написание более понятного кода - не просто хорошая практика; это то, как вы создаете более эффективный код во время выполнения.

EDIT2:

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

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

Я все еще не убежден, что заявление о переключении лучше.

работа
источник
47
Не беспокойтесь о том, чтобы доказать это быстрее. Это преждевременная оптимизация. Вы можете сэкономить миллисекунду по сравнению с тем индексом, который вы забыли добавить в базу данных, и который стоит вам 200 мс. Вы сражаетесь не в том сражении.
Рейн Хенрикс
27
@ Работа, если он на самом деле прав? Дело не в том, что он неправ, дело в том, что он прав, и это не имеет значения .
Рейн Хенрикс
2
Даже если он был прав в 100% случаев, он все равно тратит наше время.
Джереми
6
Я хочу выбить глаза, пытаясь прочитать страницу, на которую вы ссылались.
AttackingHobo
3
Что с ненавистью C ++? Компиляторы C ++ тоже становятся лучше, и большие переключатели в C ++ такие же плохие, как и в C #, и по той же причине. Если вы окружены бывшими программистами C ++, которые огорчают вас, это не потому, что они программисты на C ++, а потому, что они плохие программисты.
Себастьян Редл
39

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

Микрооптимизация должна проводиться только после устранения узких мест. Преждевременная оптимизация - корень всего зла .

Скорость поддается количественной оценке. Там мало полезной информации в «подход A быстрее, чем подход B». Вопрос «На сколько быстрее? ».

back2dos
источник
2
Абсолютная правда. Никогда не утверждайте , что что-то быстрее, всегда измеряйте. И измерять только тогда, когда эта часть приложения является узким местом производительности.
Килиан Фот
6
-1 для «Преждевременная оптимизация - корень всего зла». Пожалуйста, покажите всю цитату, а не только ту часть, которая искажает мнение Кнута.
альтернатива
2
@mathepic: я намеренно не представил это как цитату. Это предложение, как есть, мое личное мнение, хотя, конечно, не мое творение. Хотя можно отметить, что ребята из c2, похоже, считают эту часть основной мудростью.
back2dos
8
@alternative Полная цитата Кнута «Нет сомнений в том, что показатель эффективности ведет к злоупотреблениям. Программисты тратят огромное количество времени на размышления или беспокойство по поводу скорости некритических частей своих программ, и эти попытки повышения эффективности на самом деле имеют сильное негативное влияние при рассмотрении отладки и обслуживания. Мы должны забыть о малой эффективности, скажем, в 97% случаев: преждевременная оптимизация - корень всего зла ». Описывает коллегу ОП отлично. ИМХО back2dos хорошо подытожил цитату: «преждевременная оптимизация - корень всего зла»
MarkJ
2
@MarkJ 97% времени
альтернатива
27

Кого волнует, если это быстрее?

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

Однако, ремонтопригодность - цель игры, а гигантский оператор switch даже немного не поддается обслуживанию. Как вы объясните различные пути в коде новым парням? Документация должна быть такой же, как и сам код!

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

[Что касается заинтересованности: JITter лучше работает на меньших методах, поэтому гигантские операторы switch (и их изначально большие методы) повредят вашей скорости в больших сборках, IIRC.]

Эд Джеймс
источник
1
+ огромный пример преждевременной оптимизации.
ShaneC
Определенно это.
DeadMG
+1 за «заявление о гигантском переключателе даже немного неопровержимо»
Корей Хинтон
2
Гигантское заявление переключателя гораздо проще для нового парня, чтобы понять: все возможные варианты поведения собраны прямо в хороший аккуратный список. За непрямыми вызовами чрезвычайно трудно следить, в худшем случае (указатель на функцию) вам нужно искать во всей кодовой базе функции правильной подписи, а виртуальные вызовы только немного лучше (искать функции правильного имени и подписи и связанные по наследству). Но ремонтопригодность не только для чтения.
Бен Фойгт
14

Отойди от оператора switch ...

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

Дакота Север
источник
11
Это идет с оговоркой. Есть операции (функции / методы) и типы. Когда вы добавляете новую операцию, вам нужно только изменить код в одном месте для операторов switch (добавить одну новую функцию с оператором switch), но вы должны добавить этот метод ко всем классам в случае OO (нарушает open / закрытый принцип). Если вы добавляете новые типы, вы должны касаться каждого оператора switch, но в случае OO вы просто добавите еще один класс. Поэтому, чтобы принять обоснованное решение, вы должны знать, будете ли вы добавлять больше операций к существующим типам или добавлять больше типов.
Скотт Уитлок
3
Если вам нужно добавить больше операций к существующим типам в парадигме ОО, не нарушая OCP, то я считаю, что именно для этого предназначен шаблон посетителя.
Скотт Уитлок
3
@Martin - назовите имя, если хотите, но это хорошо известный компромисс. Я отсылаю вас к Чистому Коду от RC Martin. Он повторно просматривает свою статью об OCP, объясняя то, что я изложил выше. Вы не можете одновременно проектировать для всех будущих требований. Вы должны сделать выбор между тем, с большей вероятностью добавить больше операций или больше типов. ОО одобряет добавление типов. Вы можете использовать OO, чтобы добавить больше операций, если вы моделируете операции как классы, но, похоже, это входит в шаблон посетителя, который имеет свои собственные проблемы (особенно накладные расходы).
Скотт Уитлок
8
@Martin: Вы когда-нибудь писали парсер? Довольно часто есть большие переключатели, которые переключают следующий токен в буфере предварительного просмотра. Вы могли бы заменить эти переключатели виртуальными вызовами функций для следующего токена, но это было бы кошмаром обслуживания. Это редко, но иногда коммутатор является действительно лучшим выбором, потому что он хранит код, который должен быть прочитан / изменен вместе, в непосредственной близости.
nikie
1
@Martin: Вы использовали такие слова, как «never», «ever» и «Poppycock», поэтому я предположил, что вы говорите обо всех без исключения случаях, а не только о наиболее распространенных. (И кстати: люди до сих пор пишут парсеры от руки. Например, парсер CPython до сих пор пишется от руки, IIRC.)
nikie
8

Я пережил кошмар, известный как массивный конечный автомат, которым манипулируют массивные операторы переключения. Еще хуже, в моем случае, FSM охватывает три библиотеки C ++, и было совершенно ясно, что код был написан кем-то, кто разбирается в C.

Метрики, о которых вам нужно заботиться:

  • Скорость внесения изменений
  • Скорость обнаружения проблемы, когда это происходит

Мне было поручено добавить новую функцию в этот набор библиотек DLL, и я смог убедить руководство в том, что мне потребуется столько же времени, чтобы переписать 3 библиотеки DLL в одну правильно ориентированную на объект библиотеку DLL, так же, как для меня было бы исправление обезьяны. и жюри подгоняют решение к тому, что уже было там. Переписывание прошло с огромным успехом, поскольку оно не только поддерживало новую функциональность, но и было намного легче расширять. Фактически, задача, которая обычно занимает неделю, чтобы убедиться, что вы ничего не сломали, может занять несколько часов.

Так как насчет времени выполнения? Не было никакого увеличения или уменьшения скорости. Чтобы быть справедливым, наша производительность была ограничена системными драйверами, поэтому, если бы объектно-ориентированное решение было на самом деле медленнее, мы бы этого не знали.

Что не так с массивными операторами switch для языка OO?

  • Программа управления потоком отнимается от объекта, к которому она относится, и размещается вне объекта
  • Множество точек внешнего контроля переводятся во многие места, которые нужно пересмотреть
  • Неясно, где хранится состояние, особенно если коммутатор находится внутри цикла
  • Самое быстрое сравнение - это совсем не сравнение (вы можете избежать многих сравнений с хорошим объектно-ориентированным дизайном)
  • Более эффективно выполнять итерации по вашим объектам и всегда вызывать один и тот же метод для всех объектов, чем изменять код на основе типа объекта или перечисления, кодирующего тип.
Берин Лорич
источник
8

Я не покупаю аргумент производительности; это все о поддержке кода.

НО: иногда гигантский оператор switch легче поддерживать (меньше кода), чем группа небольших классов, переопределяющих виртуальную функцию (и) абстрактного базового класса. Например, если бы вы реализовали эмулятор ЦП, вы бы не реализовали функциональность каждой инструкции в отдельном классе - вы просто поместили бы ее в гигантский swtich в коде операции, возможно, вызывая вспомогательные функции для более сложных инструкций.

Практическое правило: если переключение выполняется для ТИПА, вам, вероятно, следует использовать наследование и виртуальные функции. Если переключение выполняется на ЗНАЧЕНИЕ фиксированного типа (например, код операции инструкции, как указано выше), можно оставить все как есть.

zvrba
источник
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
@Zan Lynx: ваш аргумент слишком общий. Создание объекта IAction так же сложно, как и получение целого числа действия, не сложнее и не проще. Таким образом, мы можем иметь реальный разговор, не будучи общим. Рассмотрим калькулятор. Какая разница в сложности здесь? Ответ ноль. Как все действия предварительно созданы. Вы получаете вход от пользователя, и это уже действие.
Мартин Йорк
3
@Martin: вы предполагаете приложение калькулятора GUI. Давайте вместо этого возьмем приложение для калькулятора клавиатуры, написанное для C ++ во встроенной системе. Теперь у вас есть целое число отсканированного кода из аппаратного регистра. Теперь, что менее сложно?
Zan Lynx
2
@Martin: Вы не видите, как целое число -> таблица поиска -> создание нового объекта -> вызов виртуальной функции сложнее, чем целое число -> переключатель -> функция? Как ты этого не видишь?
Zan Lynx
2
@ Мартин: Может быть, я буду. А пока объясните, как заставить объект IAction вызывать action () из целого числа без таблицы поиска.
Zan Lynx
4

Он прав, что полученный машинный код, вероятно, будет более эффективным. Компилятор существенно преобразует оператор switch в набор тестов и ветвей, в которых будет относительно мало инструкций. Существует высокая вероятность того, что код, полученный в результате более абстрактных подходов, потребует большего количества инструкций.

ОДНАКО : Это почти наверняка тот случай, когда вашему конкретному приложению не нужно беспокоиться о такого рода микрооптимизации, иначе вы бы не использовали .net в первую очередь. Для чего-либо, кроме очень ограниченных встроенных приложений или интенсивной работы процессора, вы всегда должны позволить компилятору заниматься оптимизацией. Сконцентрируйтесь на написании чистого, поддерживаемого кода. Это почти всегда имеет гораздо большее значение, чем несколько десятых наносекунды во время выполнения.

Люк Грэм
источник
3

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

Homde
источник
3

оператор switch в ООП-коде убедительно указывает на отсутствие классов

попробуйте оба способа и выполните несколько простых тестов скорости; скорее всего, разница не значительна. Если они есть, а код критичен ко времени, сохраните оператор switch

Стивен А. Лоу
источник
3

Обычно я ненавижу слово «преждевременная оптимизация», но это пахнет им. Стоит отметить, что Кнут использовал эту знаменитую цитату в контексте использования gotoоператоров для ускорения работы кода в критических областях. Это ключ: критические пути.

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

Одностороннее предпочтение switchвыражений как можно более равномерно по всей базе кода (независимо от того, обрабатывается ли большая нагрузка) - классический пример того, что Кнут называет «программистом и безумным» программистом, который целый день изо всех сил старается сохранить их «оптимизированными». "код, который превратился в отладочный кошмар в результате попытки сэкономить копейки за фунты. Такой код редко обслуживаем, не говоря уже о том, что он эффективен.

Он прав?

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

Более полезно не думать об этой стоимости с точки зрения таблиц переходов, а с точки зрения барьера оптимизации. Для полиморфизма вызов Base.method()не позволяет компилятору знать, какая функция в действительности будет вызвана, если methodона виртуальная, не запечатана и может быть переопределена. Поскольку он не знает, какая функция на самом деле будет вызываться заранее, он не может оптимизировать вызов функции и использовать больше информации при принятии решений по оптимизации, поскольку на самом деле он не знает, какая функция будет вызываться при время компиляции кода.

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

Он просто говорит свою задницу?

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

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

Копейки важны, когда у вас есть дешевая функция, которая не выполняет так много работы, которая вызывается миллиард раз в очень узкой, критичной для производительности петле. В этом случае мы сэкономим 10 миллионов долларов. Брать копейки не стоит, если у вас есть функция, вызываемая дважды, для которой одно тело стоит тысячи долларов. Не стоит тратить время на то, чтобы тратить время на копейки во время покупки автомобиля. Стоит поторговаться за копейки, если вы покупаете миллион банок содовой у производителя. Ключом к эффективной оптимизации является понимание этих затрат в их правильном контексте. Кто-то, кто пытается сэкономить копейки на каждой покупке и предлагает, чтобы все остальные пытались торговать копейки независимо от того, что они покупают, не является опытным оптимизатором.


источник
2

Похоже, ваш коллега очень обеспокоен производительностью. Возможно, в некоторых случаях большая структура case / switch будет работать быстрее, но, надеюсь, вы, ребята, проведете эксперимент, выполнив временные тесты для версии OO и версии switch / case. Я предполагаю, что версия OO содержит меньше кода и ее легче отслеживать, понимать и поддерживать. Сначала я бы поспорил с версией OO (так как обслуживание / удобочитаемость должна быть изначально более важной), и рассматривал бы версию коммутатора / корпуса только в том случае, если версия OO имеет серьезные проблемы с производительностью, и можно показать, что коммутатор / корпус создаст значительное улучшение.

FrustratedWithFormsDesigner
источник
1
Наряду с временными тестами дамп кода может помочь показать, как работает диспетчеризация методов C ++ (и C #).
С.Лотт
2

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

Например. если вы переключаетесь между Dog, Catи Elephant, а иногда Dogи Catимеете один и тот же случай, вы можете сделать так, чтобы они оба наследовали от абстрактного класса DomesticAnimalи поместили эти функции в абстрактный класс.

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

ОРГ
источник
Коммутатор также может наследовать поведение через регистр по умолчанию. «... расширяет BaseOperationVisitor» становится «по умолчанию: BaseOperation (узел)»
Сэмюэль Даниэльсон
0

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

Дональд Кнут

Торстен Мюллер
источник
0

Даже если это не плохо для удобства обслуживания, я не верю, что это будет лучше для производительности. Вызов виртуальной функции - это просто одна дополнительная косвенность (такая же, как в лучшем случае для оператора switch), поэтому даже в C ++ производительность должна быть примерно одинаковой. В C #, где все вызовы функций являются виртуальными, оператор switch должен быть хуже, поскольку у вас одинаковые издержки вызова виртуальных функций в обеих версиях.

Дирк Холсоппл
источник
1
Отсутствует "не"? В C # не все вызовы функций являются виртуальными. C # это не Java.
Бен Фойгт
0

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

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

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

Дэвид Арно
источник
0

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

Вдобавок к этому динамическая диспетчеризация часто служит «барьером оптимизации», то есть компилятор часто не сможет встроить код и оптимально распределить регистры, чтобы минимизировать потери стека и все такое причудливое, поскольку он не может понять, что Виртуальная функция будет вызываться через базовый указатель, чтобы встроить ее и выполнить всю магию оптимизации. Я не уверен, что вы даже хотите, чтобы оптимизатор был таким умным и пытался оптимизировать непрямые вызовы функций, поскольку это может привести к тому, что многие ветви кода придется генерировать отдельно в заданном стеке вызовов (функция, вызов которой foo->f()будет иметь генерировать совершенно другой машинный код от того, который вызываетbar->f() через базовый указатель, и функция, вызывающая эту функцию, должна будет генерировать две или более версии кода и т. д. - объем генерируемого машинного кода будет взрывоопасным - возможно, не так уж плохо с трассировкой JIT, которая генерирует код на лету, поскольку он отслеживает горячие пути выполнения).

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

Тем не менее, я прыгнул сюда с необычным ответом. Я хочу обосновать возможность сопровождения switchоператоров над полиморфным решением тогда и только тогда, когда вы точно знаете, что будет только одно место, которое необходимо выполнить switch.

Ярким примером является центральный обработчик событий. В этом случае у вас обычно не так много мест, где обрабатываются события, только одно (почему оно «центральное»). В этих случаях вам не выгодна расширяемость, которую обеспечивает полиморфное решение. Полиморфное решение выгодно, когда есть много мест, где можно сделать аналогичное switchутверждение. Если вы точно знаете, что будет только один, switchоператор с 15 случаями может быть намного проще, чем проектирование базового класса, унаследованного 15 подтипами, с переопределенными функциями и фабрикой для их создания, только для последующего использования в одной функции. во всей системе. В этих случаях добавление нового подтипа намного сложнее, чем добавление caseоператора в одну функцию. Во всяком случае, я бы поспорил за ремонтопригодность, а не производительность,switch заявления в этом особом случае, когда вы не получаете никакой пользы от расширяемости.


источник