Извлечение функциональности в методы или функции является обязательным условием модульности кода, читабельности и совместимости, особенно в ООП.
Но это означает, что будет сделано больше вызовов функций.
Как разделение нашего кода на методы или функции в действительности влияет на производительность в современных * языках?
* Самые популярные из них: C, Java, C ++, C #, Python, JavaScript, Ruby ...
Ответы:
Может быть. Компилятор может решить: «Эй, эта функция вызывается только несколько раз, и я должен оптимизировать скорость, поэтому я просто включу эту функцию». По сути, компилятор заменит вызов функции на тело функции. Например, исходный код будет выглядеть следующим образом.
Компилятор решает встроить
DoSomethingElse
, и код становитсяКогда функции не встроены, да, производительность вызывает вызов функции. Однако, это такой крошечный удар, что только чрезвычайно высокопроизводительный код будет беспокоиться о вызовах функций. И в подобных проектах код обычно пишется на ассемблере.
Вызовы функций (в зависимости от платформы) обычно включают несколько десятков инструкций, в том числе сохранение / восстановление стека. Некоторые вызовы функций состоят из инструкции перехода и возврата.
Но есть и другие вещи, которые могут повлиять на производительность вызова функций. Вызываемая функция не может быть загружена в кэш процессора, что приводит к потере кеша и вынуждает контроллер памяти извлекать функцию из основного ОЗУ. Это может сильно повлиять на производительность.
В двух словах: вызовы функций могут влиять или не влиять на производительность. Единственный способ узнать это - профилировать ваш код. Не пытайтесь угадать, где медленные участки кода, потому что у компилятора и аппаратного обеспечения есть невероятные хитрости. Профилируйте код, чтобы узнать местоположение медленных точек.
источник
Это вопрос реализации компилятора или среды выполнения (и его параметров), и его нельзя сказать с какой-либо определенностью.
В C и C ++ некоторые компиляторы выполняют встроенные вызовы на основе настроек оптимизации - это можно легко увидеть, изучив сгенерированную сборку, если посмотреть на такие инструменты, как https://gcc.godbolt.org/
Другие языки, такие как Java, имеют это как часть среды выполнения. Это является частью JIT и подробно рассматривается в этом вопросе SO . В частности, посмотрите на параметры JVM для HotSpot
Так что да, JIT-компилятор HotSpot встроит методы, которые соответствуют определенным критериям.
Влияние этого, трудно определить , как каждый JVM (или компилятор) может делать что - то по- другому и пытается ответить с широким мазком языка почти определенность неправильно. Влияние может быть правильно определено только путем профилирования кода в соответствующей рабочей среде и изучения скомпилированного вывода.
Это можно рассматривать как ошибочный подход: CPython не встроен, но Jython (Python работает в JVM) имеет некоторые встроенные вызовы. Аналогично, MRI Ruby не встроен в то время, как это делает JRuby, и ruby2c, который является транспортером для ruby в C ..., который затем может быть встроенным или нет в зависимости от параметров компилятора C, которые были скомпилированы.
Языки не встроены. Реализации могут .
источник
Вы ищете производительность не в том месте. Проблема с вызовами функций не в том, что они стоят дорого. Есть еще одна проблема. Вызовы функций могут быть абсолютно бесплатными, и у вас все еще будет эта другая проблема.
Это то, что функция похожа на кредитную карту. Поскольку вы можете легко использовать его, вы склонны использовать его больше, чем, возможно, следует. Предположим, вы называете это на 20% больше, чем нужно. Затем типичное большое программное обеспечение содержит несколько уровней, каждый из которых вызывает функции на нижнем уровне, поэтому коэффициент 1,2 может быть сложен по количеству уровней. (Например, если имеется пять слоев, и каждый слой имеет коэффициент замедления 1,2, составной коэффициент замедления составляет 1,2 ^ 5 или 2,5.) Это только один из способов думать об этом.
Это не означает, что вам следует избегать вызовов функций. Это означает, что когда код запущен и работает, вы должны знать, как найти и устранить потери. Есть много отличных советов о том, как это сделать на сайтах stackexchange. Это дает один из моих вкладов.
ДОБАВЛЕНО: Небольшой пример. Однажды я работал в команде над программным обеспечением на заводе, которое отслеживало ряд рабочих заданий или «рабочих мест». Была функция,
JobDone(idJob)
которая могла сказать, была ли сделана работа. Работа была выполнена, когда были выполнены все ее подзадачи, и каждая из них была выполнена, когда были выполнены все ее подзадачи. Все эти вещи отслеживались в реляционной базе данных. Один вызов другой функции может извлечь всю эту информацию, такJobDone
называемую другую функцию, увидеть, была ли выполнена работа, и выбросить все остальное. Тогда люди могут легко написать такой код:или
Видишь смысл? Функция была настолько «мощной» и простой в вызове, что ее вызывали слишком много. Таким образом, проблема с производительностью заключалась не в инструкциях, входящих и выходящих из функции. Дело в том, что должен быть более прямой способ определить, были ли выполнены работы. Опять же, этот код мог быть встроен в тысячи строк невинного кода. Попытка исправить это заранее - это то, что все пытаются сделать, но это все равно, что пытаться бросать дротики в темной комнате. Вместо этого вам нужно запустить его, а затем позволить «медленному коду» сказать вам, что это такое, просто потратив время. Для этого я использую случайную паузу .
источник
Я думаю, что это действительно зависит от языка и от функции. Хотя компиляторы c и c ++ могут встроить множество функций, это не относится к Python или Java.
Хотя я не знаю конкретных деталей для java (за исключением того, что каждый метод является виртуальным, но я предлагаю вам лучше проверить документацию), в Python я уверен, что здесь нет встроенных функций, никакой оптимизации хвостовой рекурсии и вызовов функций довольно дорого.
Python-функции - это в основном исполняемые объекты (и вы также можете определить метод call (), чтобы сделать экземпляр объекта функцией). Это означает, что при их вызове возникает много лишних затрат ...
НО
когда вы определяете переменные внутри функций, интерпретатор использует LOADFAST вместо обычной инструкции LOAD в байт-коде, делая ваш код быстрее ...
Другое дело, что когда вы определяете вызываемый объект, возможны такие шаблоны, как запоминание, и они могут значительно ускорить ваши вычисления (за счет использования большего количества памяти). В основном это всегда компромисс. Стоимость вызовов функций также зависит от параметров, потому что они определяют, сколько вещей на самом деле вам нужно скопировать в стек (таким образом, в c / c ++ обычной практикой является передача больших параметров, таких как структуры, по указателям / ссылкам вместо значений).
Я думаю, что ваш вопрос на практике слишком широк, чтобы полностью ответить на stackexchange.
Я предлагаю вам начать с одного языка и изучить расширенную документацию, чтобы понять, как вызовы функций реализуются этим конкретным языком.
Вы будете удивлены, сколько вещей вы узнаете в этом процессе.
Если у вас есть конкретная проблема, выполните измерения / профилирование и определите погоду, лучше создать функцию или скопировать / вставить эквивалентный код.
я думаю, если бы вы задали более конкретный вопрос, было бы легче получить более конкретный ответ.
источник
Я измерял накладные расходы на прямые и виртуальные вызовы функций C ++ на Xenon PowerPC некоторое время назад .
Рассматриваемые функции имели один параметр и один возврат, поэтому передача параметров происходила в регистрах.
Короче говоря, издержки прямого (не виртуального) вызова функции составляли приблизительно 5,5 наносекунд или 18 тактов по сравнению с вызовом встроенной функции. Издержки при вызове виртуальной функции составили 13,2 наносекунды или 42 такта по сравнению с встроенным.
Эти тайминги, вероятно, различаются для разных семейств процессоров. Мой тестовый код здесь ; Вы можете провести тот же эксперимент на своем оборудовании. Используйте высокоточный таймер, такой как rdtsc, для реализации CFastTimer; системное время () недостаточно точное.
источник