Это ситуация, с которой я часто сталкиваюсь как неопытный программист, и меня это интересует, особенно для моего амбициозного, требовательного к скорости проекта, который я пытаюсь оптимизировать. Для основных C-подобных языков (C, objC, C ++, Java, C # и т. Д.) И их обычных компиляторов будут ли эти две функции работать столь же эффективно? Есть ли разница в скомпилированном коде?
void foo1(bool flag)
{
if (flag)
{
//Do stuff
return;
}
//Do different stuff
}
void foo2(bool flag)
{
if (flag)
{
//Do stuff
}
else
{
//Do different stuff
}
}
В принципе, есть ли когда-либо прямой бонус / штраф за эффективность при break
игре илиreturn
ИНГ рано? Как задействован стековый фрейм? Есть ли оптимизированные особые случаи? Существуют ли какие-либо факторы (например, встраивание или размер «Do stuff»), которые могут существенно повлиять на это?
Я всегда сторонник улучшения разборчивости по сравнению с незначительными оптимизациями (я часто вижу foo1 с проверкой параметров), но это происходит так часто, что я хотел бы раз и навсегда избавиться от всех беспокойств.
И я знаю о подводных камнях преждевременной оптимизации ... тьфу, это болезненные воспоминания.
РЕДАКТИРОВАТЬ: Я принял ответ, но ответ EJP довольно лаконично объясняет, почему использование a return
практически незначительно (в сборке return
создается «ветвь» до конца функции, что очень быстро. Ветка изменяет регистр ПК и может также повлиять на кеш и конвейер, что довольно незначительно.) В этом случае, в частности, это буквально не имеет значения, потому что оба создают одну if/else
и return
ту же ветвь до конца функции.
Ответы:
Нет никакой разницы:
Нет никакой разницы в сгенерированном коде даже без оптимизации в двух компиляторах
источник
something()
всегда будет выполняться. В исходном вопросе OP имеетDo stuff
и вDo diffferent stuff
зависимости от флага. Я не уверен, что сгенерированный код будет таким же.Короткий ответ: без разницы. Сделайте себе одолжение и перестаньте об этом беспокоиться. Оптимизирующий компилятор почти всегда умнее вас.
Сконцентрируйтесь на удобочитаемости и ремонтопригодности.
Если вы хотите увидеть, что происходит, создайте их с оптимизацией и посмотрите на вывод ассемблера.
источник
x = <some number>
намного быстрее, чемif(<would've changed>) x = <some number>
могут действительно повредить ненужные ветки. С другой стороны, если это не находится внутри основного цикла чрезвычайно интенсивной операции, я бы тоже не беспокоился об этом.Интересные ответы: хотя я согласен со всеми из них (пока), в этом вопросе есть возможные коннотации, которые до сих пор полностью игнорируются.
Если простой пример, приведенный выше, дополнить выделением ресурсов, а затем проверкой ошибок с возможным высвобождением ресурсов, картина может измениться.
Рассмотрим наивный подход, который могут использовать новички:
Вышесказанное представляет собой крайнюю версию стиля преждевременного возвращения. Обратите внимание, как код становится очень повторяющимся и не обслуживаемым со временем, когда его сложность растет. В настоящее время люди могут использовать обработку исключений, чтобы поймать их.
Филипп предложил, посмотрев на приведенный ниже пример goto, использовать переключатель / case без прерывания внутри блока catch выше. Можно переключиться (typeof (e)), а затем пропустить
free_resourcex()
вызовы, но это нетривиально и требует конструктивного рассмотрения . И помните, что переключатель / корпус без разрывов точно такой же, как goto с гирляндными метками ниже ...Как отметил Марк Б., в C ++ считается хорошим стилем следовать принципу «Получение ресурсов - это инициализация» , короче RAII . Суть концепции заключается в использовании экземпляра объекта для получения ресурсов. Затем ресурсы автоматически освобождаются, как только объекты выходят из области видимости и вызываются их деструкторы. Для взаимозависимых ресурсов необходимо уделять особое внимание обеспечению правильного порядка освобождения и разработке таких типов объектов, чтобы требуемые данные были доступны для всех деструкторов.
Или в дни до исключения:
Но этот чрезмерно упрощенный пример имеет несколько недостатков: его можно использовать, только если выделенные ресурсы не зависят друг от друга (например, его нельзя использовать для выделения памяти, затем открытия дескриптора файла, а затем чтения данных из дескриптора в память. ), и он не предоставляет отдельные, различимые коды ошибок в качестве возвращаемых значений.
Чтобы код оставался быстрым (!), Компактным, легко читаемым и расширяемым, Линус Торвальдс применил другой стиль для кода ядра, который имеет дело с ресурсами, даже используя пресловутый goto таким образом, который имеет абсолютно смысл :
Суть обсуждения списков рассылки ядра заключается в том, что большинство языковых функций, которые «предпочтительнее» по сравнению с оператором goto, являются неявными gotos, такими как огромные древовидные if / else, обработчики исключений, операторы loop / break / continue и т. Д. . И goto в приведенном выше примере считаются нормальными, так как они прыгают только на небольшое расстояние, имеют четкие метки и освобождают код от другого беспорядка для отслеживания условий ошибки. Этот вопрос также обсуждался здесь, в stackoverflow .
Однако в последнем примере отсутствует хороший способ вернуть код ошибки. Я думал о добавлении a
result_code++
после каждогоfree_resource_x()
вызова и возврате этого кода, но это компенсирует некоторый выигрыш в скорости вышеупомянутого стиля кодирования. А в случае успеха вернуть 0 сложно. Может, я просто лишен воображения ;-)Итак, да, я думаю, что существует большая разница в вопросе кодирования преждевременных возвратов или нет. Но я также думаю, что это проявляется только в более сложном коде, который сложнее или невозможно реструктурировать и оптимизировать для компилятора. Обычно это происходит, когда в игру вступает распределение ресурсов.
источник
catch
содержится безотказнаяswitch
инструкция?Несмотря на то, что это не лучший ответ, производственный компилятор будет намного лучше оптимизировать, чем вы. Я бы предпочел удобочитаемость и удобство обслуживания по сравнению с такими видами оптимизации.
источник
Чтобы быть конкретным,
return
будет скомпилирован в ветку до конца метода, где будетRET
инструкция или что-то еще. Если вы его не укажете, конец блока перед блокомelse
будет скомпилирован в ветвь до концаelse
блока. Итак, вы можете видеть, что в данном конкретном случае это не имеет никакого значения.источник
Если вы действительно хотите знать, есть ли разница в скомпилированном коде для вашего конкретного компилятора и системы, вам придется скомпилировать и посмотреть сборку самостоятельно.
Однако по большому счету почти наверняка компилятор может оптимизировать лучше, чем ваша тонкая настройка, и даже если это невозможно, это вряд ли действительно будет иметь значение для производительности вашей программы.
Вместо этого напишите код наиболее понятным способом, чтобы люди могли его читать и поддерживать, и позвольте компилятору делать то, что он умеет лучше всего: генерировать лучшую сборку из вашего источника.
источник
В вашем примере отдача заметна. Что происходит с человеком, выполняющим отладку, когда возвращается страница или две выше / ниже, // где происходят разные вещи? Намного труднее найти / увидеть, когда кода больше.
источник
Я полностью согласен с blueshift: удобство чтения и ремонтопригодность в первую очередь !. Но если вы действительно обеспокоены (или просто хотите узнать, что делает ваш компилятор, что определенно является хорошей идеей в долгосрочной перспективе), вам следует поискать себя.
Это будет означать использование декомпилятора или просмотр вывода компилятора низкого уровня (например, язык сборки). На C # или любом другом языке .Net описанные здесь инструменты предоставят вам то, что вам нужно.
Но, как вы сами заметили, это, вероятно, преждевременная оптимизация.
источник
From Clean Code: A Handbook of Agile Software Craftsmanship
в коде просто заставит читателя перейти к функции и тратить время на чтение foo (логический флаг)
Лучше структурированная база кода даст вам больше возможностей для оптимизации кода.
источник
Одна школа мысли (не могу вспомнить того умника, который предложил это в данный момент) заключается в том, что все функции должны иметь только одну точку возврата со структурной точки зрения, чтобы упростить чтение и отладку кода. Я полагаю, это больше для программирования религиозных дебатов.
Одна техническая причина, по которой вы можете захотеть контролировать, когда и как завершается функция, которая нарушает это правило, заключается в том, что вы кодируете приложения реального времени и хотите убедиться, что все пути управления через функцию занимают одинаковое количество тактов.
источник
Я рад, что вы подняли этот вопрос. Вы всегда должны использовать ветки при досрочном возврате. Зачем останавливаться на достигнутом? Объедините все свои функции в одну, если можете (по крайней мере, столько, сколько сможете). Это выполнимо, если нет рекурсии. В конце концов, у вас будет одна огромная основная функция, но это то, что вам нужно / нужно для такого рода вещей. После этого переименуйте свои идентификаторы, чтобы они были как можно короче. Таким образом, когда ваш код выполняется, меньше времени тратится на чтение имен. Дальше делаем ...
источник