Intereting Posts

Почему вызовы функций PHP * так дорого?

Вызов функции в PHP стоит дорого. Вот небольшой тест, чтобы проверить его:

// create test string $string = str_repeat('a', 1000); $maxChars = 500; // with function call $start = microtime(true); for ($i = 0; $i < RUNS; ++$i) { strlen($string) <= $maxChars; } echo 'with function call: ', microtime(true) - $start, "\n"; // without function call $start = microtime(true); for ($i = 0; $i < RUNS; ++$i) { !isset($string[$maxChars]); } echo 'without function call: ', microtime(true) - $start; 

Это проверяет функционально идентичный код, используя функцию first ( strlen ), а затем без использования функции ( isset не является функцией).

Я получаю следующий вывод:

 with function call: 4.5108239650726 without function call: 0.84017300605774 

Как вы видите, реализация с использованием вызова функции более чем на 5 (5.38) раз медленнее, чем реализация, не вызывающая никакой функции.

Я хотел бы знать, почему вызов функции настолько дорог. Каково главное узкое место? Это поиск в хэш-таблице? Или что так медленно?


Я снова просмотрел этот вопрос и решил снова запустить тест, поскольку XDebug полностью отключен (а не только профилирование отключено). Это показало, что мои тесты были довольно запутанными, на этот раз с 10000000 пробегами, которые я получил:

 with function call: 3.152988910675 without function call: 1.4107749462128 

Здесь вызов функции только приблизительно в два раза (2.23) медленный, поэтому разница намного меньше.


Я только что протестировал вышеуказанный код на снимке PHP 5.4.0 и получил следующие результаты:

 with function call: 2.3795559406281 without function call: 0.90840601921082 

Здесь разница снова немного увеличилась (2.62). (Но с другой стороны время выполнения обоих методов значительно снизилось).

Функциональные вызовы стоят дорого в PHP, потому что есть много вещей, которые делаются.

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

Для простой программы:

 <?php func("arg1", "arg2"); 

Есть шесть (четыре + один для каждого аргумента) опкодов:

 1 INIT_FCALL_BY_NAME 'func', 'func'
 2 EXT_FCALL_BEGIN                                          
 3 SEND_VAL 'arg1'
 4 SEND_VAL 'arg2'
 5 DO_FCALL_BY_NAME 2          
 6 EXT_FCALL_END                                            

Вы можете проверить реализации zend_vm_def.h операций в zend_vm_def.h . Подготовьте ZEND_ к именам, например, для ZEND_INIT_FCALL_BY_NAME и ZEND_INIT_FCALL_BY_NAME поиск.

ZEND_DO_FCALL_BY_NAME особенно сложно. Тогда есть реализация самой функции, которая должна раскручивать стек, проверять типы, преобразовывать zvals и, возможно, разделять их и на фактическую работу …

Я бы утверждал, что это не так. Вы вообще не проверяете вызов функции. Вы проверяете разницу между проверкой низкого уровня за пределами (isset) и прохождением строки, чтобы подсчитать количество байтов (strlen).

Я не могу найти информацию, специфичную для PHP, но в strlen обычно реализовано что-то вроде (включая служебные вызовы функций):

 $sp += 128; $str->address = 345; $i = 0; while ($str[$i] != 0) { $i++; } return $i < $length; 

Проверка вне пределов обычно будет реализована примерно так:

 return $str->length < $length; 

Первый – это цикл. Второй – простой тест.

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

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

Следующий быстрый тест будет TRUE для числа и FALSE для чего-либо еще.

if ($x == '0'.$x) { ... }

Гораздо быстрее, чем is_numeric() и is_integer() . Опять же, только когда это имеет смысл, совершенно верно использовать некоторые оптимизации.

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

Я написал собственный скрипт для сравнения, который вызывает mt_rand () в цикле как напрямую, так и через вызов пользовательской функции:

 const LOOPS = 10000000; function myFunc ($a, $b) { return mt_rand ($a, $b); } // Call mt_rand, simply to ensure that any costs for setting it up on first call are already accounted for mt_rand (0, 1000000); $start = microtime (true); for ($x = LOOPS; $x > 0; $x--) { mt_rand (0, 1000000); } echo "Inline calling mt_rand() took " . (microtime(true) - $start) . " second(s)\n"; $start = microtime (true); for ($x = LOOPS; $x > 0; $x--) { myFunc (0, 1000000); } echo "Calling a user function took " . (microtime(true) - $start) . " second(s)\n"; 

Результаты на PHP 7 на настольном ПК на базе i6806 (более конкретно, Intel® Core ™ i5-6500 CPU @ 3.20GHz × 4):

Внутренний вызов mt_rand () занял 3.5181620121002 секунду. Вызов функции пользователя занял 7.2354700565338 секунд (ов)

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

Если ваш PHP-скрипт медленный, вероятность почти наверняка сводится к I / O или плохому выбору алгоритма, а не к вызову функции вызова. Подключение к базе данных, выполнение запроса CURL, запись в файл или даже эхо в stdout на все порядки дороже вызова функции пользователя. Если вы мне не верите, mt_rand и myfunc эхо их вывод и посмотрите, насколько медленнее работает скрипт!

В большинстве случаев лучший способ оптимизировать скрипт PHP – это минимизировать объем ввода-вывода, который он должен выполнять (выберите только то, что вам нужно в запросах БД, вместо того, чтобы полагаться на PHP, чтобы отфильтровывать ненужные строки, например), или получить он кэширует операции ввода-вывода, хотя что-то вроде memcache, чтобы уменьшить стоимость ввода-вывода для файлов, баз данных, удаленных сайтов и т. д.

Я думаю, что ответ богатого реминера на самом деле довольно точен. Вы сравниваете яблоки с апельсинами с вашим оригинальным примером. Попробуйте это вместо этого:

 <?php $RUNS = 100000; // with function call $x = ""; $start = microtime(true); for ($i = 0; $i < $RUNS; ++$i) { $x = $i.nothing($x); } echo 'with function call: ', microtime(true) - $start, "\n<br/>"; // without function call $x = ""; $start = microtime(true); for ($i = 0; $i < $RUNS; ++$i) { $x = $i.$x; } echo 'without function call: ', microtime(true) - $start; function nothing($x) { return $x; } 

Единственным отличием в этом примере является сам вызов функции. С 100 000 прогонов (как указано выше) мы видим разницу в 1% от использования вызова функции из нашего вывода:

 with function call: 2.4601600170135 without function call: 2.4477159976959 

Конечно, все это зависит от того, что делает ваша функция и что вы считаете дорогостоящим . Если nothing() возвратило $x*2 (и мы заменили нефункциональный вызов $x = $i.$x с $x = $i.($x*2) мы увидели бы потерю ~ 4% при использовании вызов функции.