Насколько важна функция вызова функции PHP?
Я относительно новичок в PHP и медленно изучаю особенности, характерные для языка. Одна вещь, которую я получаю от многих, - это то, что я (так мне сказали) использует слишком много вызовов функций, и обычно меня просят сделать что-то для их работы. Здесь два примера:
// Change this:
} catch (Exception $e) {
print "It seems that error " . $e->getCode() . " occured";
log("Error: " . $e->getCode());
}
// To this:
} catch (Exception $e) {
$code = $e->getCode();
print "It seems that error " . $code . " occured";
log("Error: " . $code);
}
Второй пример
// Change this:
$customer->setProducts($products);
// To this:
if (!empty($products)) {
$customer->setProducts($products);
}
В первом примере я нахожу, что при назначении $e->getCode()
to $code
объявлений возникают небольшие когнитивные накладные расходы; "Что такое" код "? Ах, это код из исключения". В то время как второй пример добавляет циклическую сложность. В обоих примерах я нахожу, что оптимизация достигается за счет удобочитаемости и ремонтопригодности.
Значит ли это увеличение производительности или это микро-оптимизация?
Я должен отметить, что мы застряли с PHP 5.2 прямо сейчас.
Я провел очень грубые тесты на стенде и обнаружил, что производительность вызова функции составляет порядка 10% до 70% в зависимости от характера моего стендового теста. Я соглашусь, что это важно. Но до того, как этот блок блокировки попал, произошел вызов базы данных и конечной точки HTTP. Прежде чем $products
был установлен в $customer
, был сложный вид, который произошел с массивом $products
. В конце концов эта оптимизация оправдывает затраты на упрощение чтения и обслуживания кода? Или, хотя эти примеры являются упрощениями, кто-нибудь может найти 2-й пример так же легко или проще для чтения чем первый (я являюсь wiener)?
Может ли кто-нибудь привести какие-либо хорошие статьи или исследования об этом?
Изменить:
Пример тестового теста:
<?php
class Foo {
private $list;
public function setList($list) {
$this->list = $list;
}
}
$foo1 = new Foo();
for($i = 0; $i < 1000000; $i++) {
$a = array();
if (!empty($a))
$foo1->setList($a);
}
?>
Запустите этот файл с помощью команды time
. На одной конкретной машине она занимает в среднем 0,60 секунды после нескольких прогонов. Комментирование if (!empty($a))
заставляет его выполнять в среднем 3,00 секунды для запуска.
Разъяснение. Это примеры. Первый пример демонстрирует ужасную обработку исключений и возможное нарушение DRY за счет простого примера, не относящегося к домену.
Ответы
Ответ 1
Каноническая реализация PHP выполняется очень медленно, потому что ее легко реализовать и приложения, на которые нацелены PHP, не требуют сырой производительности, например, быстрых вызовов функций.
Возможно, вы захотите рассмотреть другие PHP-реализации.
Если вы пишете приложения, которые вы должны писать в PHP (данные дампа от БД к браузеру через сеть), то служебные данные вызова функции несущественны. Конечно, не изо всех сил пытайтесь дублировать код, потому что вы боялись использовать функцию, было бы слишком много накладных расходов.
Ответ 2
Накладные расходы на функцию PHP составляют ровно 15.5355%.
:) Просто помешивая горшок.
Серьезно, вот пара отличных ссылок на эту тему:
Возможно ли иметь слишком много функций в приложении PHP?
функции против повторяющегося кода
Поддерживаемость кода по сравнению с обсуждениями скорости на этих ссылках затрагивает (возможно, более важный) вопрос, подразумеваемый OP, но просто добавляет немного данных, которые также могут быть уместными и, надеюсь, полезными для людей, которые сталкиваются с этой нитью в будущем, вот результаты запуска приведенного ниже кода на Macbook Pro 2011 года (с очень небольшим количеством дискового пространства и слишком большим количеством запущенных программ).
Как отмечалось в другом месте, важным соображением при принятии решения о том, следует ли вызывать функцию или поместить код "in-line", является то, сколько раз функция будет вызываться из определенного блока кода. Чем больше раз будет вызываться функция, тем больше стоит считать выполнение работы в строке.
Результаты (раз в секундах)
Метод функции вызова | Метод In-Line | Разница | Процент разных
1,000 итераций (4 пробега)
0.0039088726043701 | 0.0031478404998779 | 0.00076103210449219 | 19,4694
0.0038208961486816 | 0.0025999546051025 | 0.0012209415435791 | 31,9543
0.0030159950256348 | 0.0029480457305908 | 6.7949295043945E-5 | 2,2530
0.0031449794769287 | 0.0031390190124512 | 5.9604644775391E-6 | 0,1895
1,000,000 итераций (4 прогона)
3.1843111515045 | 2.6896121501923 | 0,49469900131226 | 15,5355
3.131945848465 | 2.7114839553833 | 0.42046189308167 | 13,4249
3.0256152153015 | 2.7648048400879 | 0,26081037521362 | 8,6201
3.1251409053802 | 2.7397727966309 | 0.38536810874939 | 12,3312
function postgres_friendly_number($dirtyString) {
$cleanString = str_ireplace("(", "-", $dirtyString);
$badChars = array("$", ",", ")");
$cleanString = str_ireplace($badChars, "", $cleanString);
return $cleanString;
}
//main
$badNumberString = '-$590,832.61';
$iterations = 1000000;
$startTime = microtime(true);
for ($i = 1; $i <= $iterations; $i++) {
$goodNumberString = postgres_friendly_number($badNumberString);
}
$endTime = microtime(true);
$firstTime = ($endTime - $startTime);
$startTime = microtime(true);
for ($i = 1; $i <= $iterations; $i++) {
$goodNumberString = str_ireplace("(", "-", $badNumberString);
$badChars = array("$", ",", ")");
$goodNumberString = str_ireplace($badChars, "", $goodNumberString);
}
$endTime = microtime(true);
$secondTime = ($endTime - $startTime);
$timeDifference = $firstTime - $secondTime;
$percentDifference = (( $timeDifference / $firstTime ) * 100);
Ответ 3
Никто еще не обсуждал, как аппаратное обеспечение сервера связано с служебными данными вызова функции.
Когда вызывается функция, все регистры CPU содержат данные, относящиеся к текущей точке выполнения. Все регистры процессора должны быть сохранены в памяти (как правило, в стеке процесса), или нет никакой надежды когда-либо вернуться к этой точке выполнения и возобновить выполнение. При возврате из функции все регистры CPU должны быть восстановлены из памяти (как правило, в стеке процесса).
Итак, можно увидеть, как строка вложенных вызовов функций может добавить служебные данные в процесс. Регистры CPU должны сохраняться снова и снова в стеке и восстанавливаться снова и снова, чтобы вернуться от функций.
Это действительно источник накладных расходов на вызовы функций. И если аргументы функции переданы, все они должны быть дублированы до вызова функции. Поэтому передача огромных массивов в качестве аргументов функции является плохим дизайном.
Исследования по объектно-ориентированному PHP выполнялись на основе использования геттеров/сеттеров. Удаление всех геттеров/сеттеров сокращает время выполнения примерно на 50%. И это просто связано с накладными вызовами функций.
Ответ 4
Я вас путаю терминологию. Как правило, вызов вызова функции
означает накладные расходы, связанные с вызовом возврата из функции.
Вместо обработки inline. Это не общая стоимость вызова функции.
Его просто затраты на подготовку аргументов и возврат значений и выполнение ветки.
Проблема заключается в том, что PHP и другие слабо типизированные языки стиля сценариев действительно плохо определяют, имеют ли функции побочные эффекты. Таким образом, вместо сохранения результатов функции в качестве темпа, они будут выполнять несколько вызовов. Если функция выполняет что-то сложное, это будет очень неэффективно.
Итак, в нижней строке: вызов функции один раз и сохранение и повторное использование результата!
Не вызывайте одну и ту же функцию несколько раз с теми же аргументами. (без уважительной причины)