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;
这首先使用函数(strlen
)测试功能相同的代码,然后不使用函数(isset
不是函数)。
我得到以下输出:
with function call: 4.5108239650726
without function call: 0.84017300605774
正如您所看到的,使用函数调用的实现比不调用任何函数的实现慢了五(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)。 (但是在这方面,两种方法的执行时间都显着下降了。)
答案 0 :(得分:43)
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_
添加到名称中,例如ZEND_INIT_FCALL_BY_NAME
和搜索。
ZEND_DO_FCALL_BY_NAME
特别复杂。然后是函数本身的实现,它必须展开堆栈,检查类型,转换zval并可能将它们分开并实际工作......
答案 1 :(得分:5)
我认为他们不是。你实际上根本没有测试函数调用。您正在测试低级越界检查(isset)和遍历字符串以计算字节数(strlen)之间的差异。
我找不到任何特定于PHP的信息,但strlen通常实现类似(包括函数调用开销):
$sp += 128;
$str->address = 345;
$i = 0;
while ($str[$i] != 0) {
$i++;
}
return $i < $length;
越界检查通常会实现如下:
return $str->length < $length;
第一个是循环。第二个是简单的测试。
答案 2 :(得分:4)
调用用户函数的开销真的那么大吗?或者说它现在真的那么大吗?自从最初提出这个问题以来,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";
基于2016年份i5桌面的PHP 7(更具体地说,英特尔®酷睿™i5-6500 CPU @ 3.20GHz×4)的结果如下:
内联调用mt_rand()花了3.5181620121002秒 调用用户函数需要7.2354700565338秒
调用用户函数的开销似乎大约是运行时的两倍。但它需要1000万次迭代才能变得特别引人注目。这意味着在大多数情况下,内联代码和用户功能之间的差异可能可以忽略不计。您应该只关心程序最内层循环中的那种优化,即使这样,只有基准测试显示出明显的性能问题。其他任何东西都是micro-optimisation,这对于源代码中增加的复杂性几乎没有产生有意义的性能优势。
如果你的PHP脚本很慢,那么几乎可以肯定的是它会降低到I / O或算法选择不当而不是函数调用开销。连接到数据库,执行CURL请求,写入文件甚至只是回显到stdout都比调用用户函数要贵几个数量级。如果你不相信我,让mt_rand和myfunc回显他们的输出,看看脚本运行的速度有多慢!
在大多数情况下,优化PHP脚本的最佳方法是最小化它必须执行的I / O量(例如,只选择数据库查询中需要的内容,而不是依赖于PHP来过滤掉不需要的行)或者通过memcache之类的东西来缓存I / O操作,以降低文件,数据库,远程站点等的I / O成本
答案 3 :(得分:3)
由于@Artefacto上面的完美解释,函数调用很昂贵。请注意,它们的性能与所涉及的参数/参数的数量直接相关。这是我在开发自己的应用程序框架时非常关注的一个领域。当它有意义并且可以避免函数调用时,我会这样做。
一个这样的例子是我的代码中最近用一个简单的布尔测试替换is_numeric()
和is_integer()
调用,特别是当可能对这些函数进行多次调用时。虽然有些人可能认为这种优化毫无意义,但我发现通过这种优化工作,我网站的响应能力有了显着的提升。
对于数字,以下快速测试将为TRUE,对于其他任何内容,则为FALSE。
if ($x == '0'.$x) { ... }
比is_numeric()
和is_integer()
快得多。同样,只有在有意义的情况下,使用某些优化才是完全有效的。
答案 4 :(得分:3)
我认为富豪的答案实际上非常准确。您将苹果与橙子与原始示例进行比较。试试这个:
<?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%。