【问题标题】:Performance of static methods vs. functions静态方法与函数的性能
【发布时间】:2010-12-01 03:48:05
【问题描述】:

在 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 万次,但事实是它就在那里。

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

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

【问题讨论】:

  • 您的测试中是否启用了优化器/字节码缓存?
  • 尝试使用适当的优化框架重复测试。看看你会得到什么。
  • @Will Bickford - 在不同的机器上使用 eaccellerator,$object->method() 以大约 95% 的静态调用速度(或 105% 的工作时间)运行。
  • 您在测试哪个版本的 PHP?在 5.2.10 中,没有加速器和空函数体,经过 10 次试验后,静态、对象和普通函数调用的标准化平均时间分别为 1.30、1.16 和 1。
  • 我在答案中添加了一个简单的测试,以便任何人都可以轻松查看结果并进行思考:stackoverflow.com/questions/1472721/…

标签: php performance oop


【解决方案1】:

我已经跟进并在 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

【讨论】:

    【解决方案2】:

    我正在跟进 Morgan Touvrey 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 的运行速度似乎比其他版本慢很多。我找到了他们最低的数字,但它也进入了低 0.2#### 的数字。目前还没有 7.4。

    【讨论】:

      【解决方案3】:

      显然这一点已在 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 这样的快速语言是多么令人惊奇(与其他动态语言相比,你可以看到 here或者here) 可以反复优化:

      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 中的类型提示意味着 more 检查 Zend 引擎,而不是像我们中的一些人所期望的那样 less。未来,我们可能会在这一点上获得更多的运行时优化(就像 HHVM 的运行时代码分析和 JiT 方法一样)。不要忘记 PHP7 还很年轻,为这个版本所做的所有清理工作都允许将来在功能和性能方面得到很大的改进。

      ? HHVM

      针对 HHVM 3.7.1 的测试仍然表明 HHVM 很容易在这类基准测试中获胜,在这里您可以看到 JiT 编译的好处(JiT 是未来版本的“计划”功能PHP,我们可能会在 7.x 或 8.x 分支中获得它。Zend had created a PoC, as an OpCache extension):

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

      对于 HHVM,函数和静态方法的时序非常相似,这可以让我们认为,在内部,它们几乎是相同的东西(毕竟,静态方法与命名空间函数非常相似)。与其他实例相比,实例时间是“灾难性的”。这表明 HHVM 和 ZE 是非常不同的引擎。

      结论?

      无法保证其中一种做法(静态/实例)会永远保持更快。使用在软件设计方面看起来最好的东西,并在现有应用程序中保持连贯的代码。

      如果您有选择,和/或如果您正在编写库等,那么也许您可以使用实例方法,它对 DI 环境更友好,并且为使用您的 API 的开发人员提供了更多控制权。

      如果您只是提供实用功能(例如 npm 生态系统中的那些小包),那么您可以使用命名空间功能(但请注意 PHP still doesn't have function autoloading,这意味着 Composer can't lazy-load your library 就像它对 PSR-0/4 所做的那样)

      【讨论】:

      • 你能提供一个 3v4l 链接到你运行的内容吗?
      • 请。代码是什么?是问题中的那个吗?或者是完全不同的东西?我们在这里测量空气分子。对测量仪器的完全控制至关重要。
      • 你能用针对 PHP 7.0-release 和 7.1 和 7.2 以及 7.3 和 7.4 的测试来更新你的代码吗?我喜欢基准测试!
      • 您能用 PHP 7.1 、 7.2 、 7.3 和 7.4 再次运行它吗?我会很感激的
      • @Sz:代码是一样的。只是测试了函数/实例/静态方法之间的差异,仅此而已。每次,我都会多次运行该程序以确保获得稳定的结果。测试是在 Linux 下运行的,同时使用尽可能少的并发程序。
      【解决方案4】:

      调用静态方法时曾经有很大的惩罚 - 但它已在 5.4.0 中修复 - 请参阅大量测试结果 http://www.micro-optimization.com/global-function-vs-static-method。

      【讨论】:

        【解决方案5】:

        您的测试中有一些错误。对于一个设计为同时与多个用户一起工作的网站,您必须为每个用户创建一个对象。要在测试中运行该对象的方法,您应该:

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

        如果你的对象有更多的属性和方法,那么创建它的速度会更慢,而且 PHP 会使用更多的内存。静态方法不会有这个问题,因此在很多情况下使用静态方法是更好的选择。例如,一个带有一些方便工具的类,这些工具带有用于常见任务的静态方法。

        【讨论】:

          【解决方案6】:

          我在我的机器上重复了多次测试,令人惊讶的是你是对的!

          In PHP calling methods of static class seems to be slower than calling object methods. Click here for simple test.

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

          我什至尝试将对象方法和静态方法放在同一个类中,而 static 方法仍然导致 SLOWER !!!

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

          遗憾的是,我对原因一无所知。 也许 PHP 需要更多时间来查找 static 方法的定义。

          作为旁注,我只能说,在现实生活中的应用程序中,它通常会在调用其方法之一之前创建对象。因此,您的测试应该考虑到将静态调用循环与每次(或至少某些时候)[*]创建对象的循环进行比较:

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

          因此显然比static 调用慢。

          for($i=0; $i<10*1000*1000; $i++)
          { 
             SomeClass::doTest($i);
          }
          

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

          【讨论】:

            【解决方案7】:

            对于静态方法,PHP 必须检查该方法是否可以从调用上下文(公共、受保护、私有)中调用。这很可能是导致开销或至少部分开销的原因,因为经典函数调用不需要 PHP 执行这种检查。

            【讨论】:

            • 有道理 - 但是,调用对象方法更快,并且适用相同的规则......
            • 也许 PHP 检查是否可以从当前上下文调用特定对象方法,仅一次,并且只要执行循环保持在同一上下文中,就将该信息存储在内存中......但是对于静态方法不这样做。伙计,你让我想知道为什么,现在 :) 你可以在 PHP 开发人员列表上问这个问题!
            【解决方案8】:

            我已经有一段时间没有做任何 PHP 了,但这可能与您在其他编程环境中所期望的相似。

            很可能静态方法每次调用时都需要在幕后构造一些 SomeClass 对象,而该函数可以在没有任何启动成本的情况下执行。创建对象可能代价高昂,具体取决于许多因素:垃圾收集器/引用计数器对现有对象的破坏、导致碎片的内存压力、C 运行时中的次优内存分配策略等。

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

            【讨论】:

            • 用对象方法基准更新问题 - 并不是我想象的结果。
            • 为什么调用静态方法需要实例化对象?方法是静态的,没有对象!
            • @Jesse 对象的创建可以隐式使用静态方法。由于它被声明为类的一部分,那么类到对象的实例化仍然涉及系统如何执行方法。
            • @BrianLyttle 你能详细说明一下吗?我不具体了解 Zend 引擎,但通常只是在类中并不意味着需要实例化对象。在这方面,静态方法可以与普通全局函数一样处理。
            • 我知道这有点老了,但这对我来说似乎是最可能的原因。静态方法调用仍然需要构建类,因为如果我从公共静态方法调用私有静态方法怎么办?对象本身不是为静态调用而构建的,但类仍然需要。
            猜你喜欢
            • 2012-08-30
            • 2012-05-09
            • 1970-01-01
            • 2011-03-02
            • 2017-12-19
            • 2011-12-29
            • 2012-03-28
            • 1970-01-01
            相关资源
            最近更新 更多