静态方法和函数的性能比较

66

在 PHP 中,与我最初想象的不同,调用静态方法与简单函数相比,存在一定的开销。

在一个非常简单的测试中,静态方法的开销超过了调用时间的 30%(该方法只返回参数):

// bench static method
$starttime = microtime(true);
for ($i = 0; $i< 10*1000*1000; $i++)
    SomeClass::doTest($i);

echo "Static Time:   " , (microtime(true)-$starttime) , " ms\n";

// bench object method
$starttime = microtime(true);

for ($i = 0; $i< 10*1000*1000; $i++)
    $someObj->doTest($i);

echo "Object Time:   " , (microtime(true)-$starttime) , " ms\n";

// bench function
$starttime = microtime(true);

for ($i = 0; $i< 10*1000*1000; $i++)
    something_doTest($i);

echo "Function Time: " , (microtime(true)-$starttime) , " ms\n";

输出:

Static Time:   0.640204906464 ms
Object Time:   0.48961687088 ms
Function Time: 0.438289880753 ms

我知道实际时间仍然可以忽略不计,除非我确实调用了100万次某个东西,但事实就是存在这种情况。

有人愿意尝试解释背后发生的事情吗?

更新:
- 添加了对象方法bench


你的测试中是否启用了任何优化器/字节码缓存? - Dennis C
尝试使用优化框架重复测试。然后查看结果。 - Will Bickford
你正在测试哪个版本的PHP?在5.2.10版本中,没有加速器和空函数体,在进行了10次试验后,我得到了静态、对象和普通函数调用的标准化平均时间分别为1.30、1.16和1。 - outis
1
我在我的回答中添加了一个简单的测试,以便任何人都可以轻松地查看结果并思考:https://dev59.com/VnM_5IYBdhLWcg3wNwUv#7045679 - Marco Demaio
有人已经向 PHP 开发团队报告了这个问题吗?哈哈 - Netorica
显示剩余2条评论
8个回答

67

显然,这个问题在PHP的后续版本中已经修复(5.5.12)。

我运行了OP的代码(空方法),并得到以下结果:

Static Time:   1.0153820514679 ms
Object Time:   1.100515127182 ms

编辑:八个月后和一些版本更新之后...

看到Zend和社区如何努力提高PHP性能是很有趣的。

PHP 5.6

这里是使用PHP 5.6.9(ZE 2.6)进行的相同基准测试:

Static Time:   0.97488021850586 ms
Object Time:   1.0362110137939 ms
Function Time: 0.96977496147156 ms

对于一次运行,"对象时间"甚至比静态时间更快,所以现在它们非常接近。更好的是,我们可以看到对象几乎和函数一样快!

PHP 7.0

我还编译了PHP 7.0 alpha 1 (ZE 3.0),令人惊奇的是,像PHP这样快速的语言 (与您可以在这里这里看到的其他动态语言相比) 可以不断地进行优化:

Static Time:   0.33447790145874 ms
Object Time:   0.30291485786438 ms
Function Time: 0.2329089641571 ms

使用PHP7,基本函数得到了很大的优化,"静态时间"再次比"实例/对象时间"慢。

编辑,2015年10月 一年后:PHP 7.0 RC5。现在,"静态时间"更快了。 需要注意的重要事情:标量类型提示(PHP7的新功能)带来了显著的开销,它比原来慢了16%(类型提示不会使您的代码变慢16%,只是当您的代码仅由函数调用组成时才会变慢 ;) 在实际应用中,这是可以忽略不计的)。这样的开销可能看起来不合逻辑,但是当您了解动态类型是PHP核心时,就不那么令人惊讶了。与其他更静态的语言相反,在PHP中进行类型提示意味着Zend引擎需要进行更多的检查,而不是像我们中的一些人所期望的那样进行更少的检查。将来,我们可能会在这一点上获得更多的运行时优化(就像HHVM的运行时代码分析和JiT方法一样)。不要忘记,PHP7还很年轻,为此版本进行的所有清理工作都可以在未来的功能和性能方面获得巨大的改进。

HHVM

一项针对HHVM 3.7.1的测试仍然表明,HHVM在这种类型的基准测试中轻松获胜,这里可以看到JiT编译的好处(JiT是未来版本PHP的“计划”功能,我们可能会在7.x或8.x分支中获得它。Zend已经创建了一个PoC,作为OpCache扩展):

Static Time:   0.070882797241211 ms
Object Time:   0.23940300941467 ms
Function Time: 0.06760311126709 ms

对于HHVM,函数和静态方法的时间非常相似,这让我们认为,在内部,它们几乎是相同的东西(毕竟,静态方法与命名空间函数非常相似)。与其他方法相比,实例方法的时间是“灾难性”的。这表明了HHVM和ZE引擎之间的巨大差异。
结论? 没有保证其中一种做法(静态/实例)会永远保持更快。在软件设计方面使用最佳实践,并在现有应用程序中保持一致的代码。
如果您有选择,或者如果您正在编写库等,则可以使用实例方法,它更加友好于DI环境,并且为使用您的API的开发人员提供更多控制。
如果您只提供实用程序函数(例如npm生态系统中的那些小包),则可以使用命名空间函数(但请注意,PHP仍然没有函数自动加载,这意味着Composer无法像PSR-0/4一样惰性加载您的库)。

3
你能提供一个3v4l的链接,让我查看你运行的内容吗? - a coder
你能否更新你的代码,针对 PHP 7.0-release、7.1、7.2、7.3 和 7.4 进行测试?我喜欢基准测试! - hanshenrik
请您再次使用 PHP 7.1、7.2、7.3 和 7.4 运行它,我将不胜感激。 - GameO7er
@a 程序员:我认为 3v4l 那时还不存在,而且我不再使用 PHP。如果有人想要修改,请提出修改意见或发布自己的答案。 - Morgan Touverey Quilling
1
@Sz:代码是一样的。只是测试了函数/实例/静态方法之间的差异,没有多余的东西。每次运行程序时,我都运行了多次以确保结果稳定。测试是在Linux下进行的,并尽可能少地运行并发程序。 - Morgan Touverey Quilling
显示剩余2条评论

23

9

我在我的电脑上多次重复了这个测试,出乎意料的是,你是对的!

在 PHP 中调用 static 类的方法似乎比调用对象方法慢。点击此处进行简单测试。

运行测试的代码在上面的链接中。

我甚至尝试将对象方法和静态方法都放在同一个类中,但是 static 方法仍然更慢!!!

此时我想知道调用继承类的 static 方法会有多慢,因为继承会增加延迟。

不幸的是,我对原因一无所知。或许 PHP 在查找 static 方法的定义时需要更多时间。

另外,我只能说在实际应用中,通常会在调用其方法之前创建对象。因此,你的测试应该考虑将静态调用循环与每次(或至少某些次)创建对象的循环进行比较:

for($i=0; $i<10*1000*1000; $i++)
{ 
   $someObj = new someObj();
   $someObj->doTest($i); 
}

这显然比静态调用慢。
for($i=0; $i<10*1000*1000; $i++)
{ 
   SomeClass::doTest($i);
}

[*] 问题是:为了模拟真实世界应用程序中发生的情况,需要多长时间算是“有时”?这很难说!

6

我已经跟进并在PHP 8.0.3上进行了大量迭代的相同测试。

Opcache在此测试中没有太大差别。

没有opcache:

Function Time:  0.15400409698486 ms
Static Time:    0.15216994285583 ms
Object Time:    0.19552803039551 ms
Function Time:  0.1428279876709 ms
Static Time:    0.15206789970398 ms
Object Time:    0.22962498664856 ms
Function Time:  0.14341592788696 ms
Static Time:    0.15271997451782 ms
Object Time:    0.22965002059937 ms
Function Time:  0.1877110004425 ms
Static Time:    0.1523380279541 ms
Object Time:    0.2297830581665 ms
Function Time:  0.14280891418457 ms
Static Time:    0.15206098556519 ms
Object Time:    0.22957897186279 ms
Function Time:  0.14343619346619 ms
Static Time:    0.15272903442383 ms
Object Time:    0.22955703735352 ms
Function Time:  0.14328694343567 ms
Static Time:    0.15257477760315 ms
Object Time:    0.22901511192322 ms
Function Time:  0.14302086830139 ms
Static Time:    0.15233588218689 ms
Object Time:    0.22931504249573 ms
Function Time:  0.14283490180969 ms
Static Time:    0.15209102630615 ms
Object Time:    0.22963285446167 ms
Function Time:  0.14345097541809 ms
Static Time:    0.1527111530304 ms
Object Time:    0.22959303855896 ms

使用 Opcache:

Function Time:  0.15897798538208 ms
Static Time:    0.15508103370667 ms
Object Time:    0.20733213424683 ms
Function Time:  0.14364719390869 ms
Static Time:    0.15376496315002 ms
Object Time:    0.18648386001587 ms
Function Time:  0.142982006073 ms
Static Time:    0.15293192863464 ms
Object Time:    0.20651602745056 ms
Function Time:  0.14292907714844 ms
Static Time:    0.15280795097351 ms
Object Time:    0.18663787841797 ms
Function Time:  0.14208316802979 ms
Static Time:    0.15290093421936 ms
Object Time:    0.20616102218628 ms
Function Time:  0.14288401603699 ms
Static Time:    0.15276694297791 ms
Object Time:    0.1861629486084 ms
Function Time:  0.14292597770691 ms
Static Time:    0.15292882919312 ms
Object Time:    0.20615196228027 ms
Function Time:  0.14286112785339 ms
Static Time:    0.1527988910675 ms
Object Time:    0.18700098991394 ms
Function Time:  0.14315795898438 ms
Static Time:    0.15318417549133 ms
Object Time:    0.20666813850403 ms
Function Time:  0.14300584793091 ms
Static Time:    0.15291309356689 ms
Object Time:    0.18714189529419 ms

5

你的测试中有些问题。如果你要设计一个可以同时支持多个用户使用的网站,那么你需要为每个用户创建一个对象。在测试中运行该对象的方法时,您应该使用以下代码:

for($i=0; $i<10*1000*1000; $i++)
{ 
   $someObj = new someObj();
   $someObj->doTest($i); 
}

如果您的对象拥有更多的属性和方法,那么创建它会更慢,PHP会使用更多的内存。静态方法就不会有这个问题,因此在许多情况下使用静态方法是更好的选择。例如,一个包含一些有用工具的类可使用静态方法完成常见任务。


3

我已经有一段时间没有使用PHP了,但这与其他编程环境中的情况可能相似。

静态方法在每次调用时可能需要在后台构造SomeClass对象,而函数可以直接执行而无需任何启动成本。创建一个对象可能很昂贵,具体取决于许多因素:垃圾收集器/引用计数器销毁现有对象,内存压力导致碎片化,C运行时中不优化的内存分配策略等等。

有趣的是比较现有对象方法的性能。为此,请创建SomeClass实例并重复调用实例方法。


更新了带有对象方法基准测试的问题 - 不是我想象中的结果。 - J.C. Inacio
为什么调用静态方法需要实例化对象?该方法是静态的,没有对象! - Jesse
@Jesse 对象的创建可以隐含在静态方法的使用中。由于它被声明为类的一部分,因此类到对象实例化仍然涉及系统执行方法的方式。 - Brian Lyttle
@BrianLyttle 能否详细说明一下?我不太了解Zend引擎,但是通常只是处于类中并不意味着必须实例化一个对象。在这方面,静态方法可以像普通的全局函数一样处理。 - Jesse
1
我知道这可能有点老了,但对我来说这似乎是最有可能的原因。静态方法调用仍然需要构建类,因为如果我从公共静态方法调用私有静态方法怎么办?对象本身并不为静态调用而构建,但类仍然需要构建。 - Jeff Lambert

2

我正在跟随摩根·图弗里·奎林(Morgan Touverey Quilling)的步伐,但使用PHP 7。进行了3次迭代以防第一次运行需要更长时间。包括所有类,因为这可能是现实情况。所有包含的文件都只返回输入。

include 'lib/global.php';
include 'SomeClass.php';
include 'StaticTest.php';

$someObj = new SomeClass();

$starttime = microtime(true);
for ($i = 0; $i< 10*100000; $i++)
    StaticTest::doStaticTest($i);

echo "<br>Static Time:   " , (microtime(true)-$starttime) , " ms\n";

// bench object method
$starttime = microtime(true);

for ($i = 0; $i< 10*100000; $i++)
    $someObj->doObjTest($i);

echo "<br>Object Time:   " , (microtime(true)-$starttime) , " ms\n";

// bench function
$starttime = microtime(true);

for ($i = 0; $i< 10*100000; $i++)
    something_doTest($i);

echo "<br>Function Time: " , (microtime(true)-$starttime) , " ms\n";

echo "<br>Static Time:   " , (microtime(true)-$starttime) , " ms\n";

// bench object method
$starttime = microtime(true);

for ($i = 0; $i< 10*100000; $i++)
    $someObj->doObjTest($i);

echo "<br>Object Time:   " , (microtime(true)-$starttime) , " ms\n";

// bench function
$starttime = microtime(true);

for ($i = 0; $i< 10*100000; $i++)
    something_doTest($i);

echo "<br>Function Time: " , (microtime(true)-$starttime) , " ms\n";

echo "<br>Static Time:   " , (microtime(true)-$starttime) , " ms\n";

// bench object method
$starttime = microtime(true);

for ($i = 0; $i< 10*100000; $i++)
    $someObj->doObjTest($i);

echo "<br>Object Time:   " , (microtime(true)-$starttime) , " ms\n";

// bench function
$starttime = microtime(true);

for ($i = 0; $i< 10*100000; $i++)
    something_doTest($i);

echo "<br>Function Time: " , (microtime(true)-$starttime) , " ms\n";

请注意,这是在我的一个网络主机上完成的,因为更容易切换PHP版本,所以可能会有一些噪音。 PHP 7.0.33
Static Time:   0.14076709747314 ms
Object Time:   0.16203689575195 ms
Function Time: 0.13194108009338 ms
Static Time:   0.13194918632507 ms
Object Time:   0.1779100894928 ms
Function Time: 0.13044309616089 ms
Static Time:   0.13045001029968 ms
Object Time:   0.16074585914612 ms
Function Time: 0.13029479980469 ms 

PHP 7.1.29

Static Time:   0.13407206535339 ms
Object Time:   0.13267111778259 ms
Function Time: 0.1302649974823 ms
Static Time:   0.13027906417847 ms
Object Time:   0.1390438079834 ms
Function Time: 0.16873598098755 ms
Static Time:   0.16874289512634 ms
Object Time:   0.13901305198669 ms
Function Time: 0.12576103210449 ms 

PHP 7.2.18:

Static Time:   0.1657600402832 ms
Object Time:   0.15700101852417 ms
Function Time: 0.1484169960022 ms
Static Time:   0.14842295646667 ms
Object Time:   0.16168689727783 ms
Function Time: 0.17508292198181 ms
Static Time:   0.17508983612061 ms
Object Time:   0.19771790504456 ms
Function Time: 0.1468551158905 ms 

PHP 7.3.5

Static Time:   0.10701704025269 ms
Object Time:   0.097011089324951 ms
Function Time: 0.075740098953247 ms
Static Time:   0.07575798034668 ms
Object Time:   0.083790063858032 ms
Function Time: 0.072473049163818 ms
Static Time:   0.072479009628296 ms
Object Time:   0.081503868103027 ms
Function Time: 0.071882963180542 ms 

PHP 7.2 在平均情况下似乎运行得比其他版本慢很多。我找到了它们的最低数值,但也达到了低于 .2#### 的水平。目前还没有 7.4 版本。


2

在静态方法的情况下,PHP必须检查该方法是否可以从调用上下文(public、protected、private)中调用。这很可能是导致开销的原因,或者至少是其一部分,因为经典的函数调用不需要PHP执行那种类型的检查。


1
这是有道理的-然而,调用对象方法更快,并且同样的规则适用... - J.C. Inacio
也许 PHP 会检查特定对象方法是否可以从当前上下文中调用,仅一次,并将该信息存储在内存中,只要执行循环保持在相同的上下文中...但对于静态方法则不会这样做。天啊,你让我想知道为什么 :) 这是你可以在 PHP 开发者列表上问的问题! - Nicolas

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接