【问题标题】:Performance of variable expansion vs. sprintf in PHPPHP中变量扩展与sprintf的性能
【发布时间】:2011-11-01 02:47:43
【问题描述】:

关于性能,做有什么区别:

$message = "The request $request has $n errors";

$message = sprintf('The request %s has %d errors', $request, $n);

在 PHP 中?

我会说调用函数涉及更多的东西,但我不知道 PHP 在幕后做了什么来扩展变量名称。

谢谢!

【问题讨论】:

    标签: php string performance printf variable-expansion


    【解决方案1】:

    没关系。

    任何性能提升都是微不足道的,以至于您只会在 10000 或 100000 次迭代中看到它(作为百分之几秒的改进)——即使是这样。

    有关具体数字,请参阅this benchmark。您可以看到它必须使用 100,000 次函数调用生成 1MB+ 的数据才能在数百毫秒内实现可测量的差异。几乎不是现实生活中的情况。即使是最慢的方法(“sprintf() with positional params”)也只需要 0.00456 毫秒,而最快的方法是 0.00282 毫秒。对于需要 100,000 次字符串输出调用的任何操作,您将有其他因素(例如网络流量)比通过优化可以节省的 100 毫秒慢一个数量级。

    使用任何使您的代码对您和其他人来说最易读和可维护的方式。就我个人而言,sprintf() 方法是一个不错的想法 - 我必须考虑自己开始使用它。

    【讨论】:

    • sprintf 如果您必须显示很多变量,实际上很有用,其中双引号字符串可能会变成象形文字(特别是关联数组)。但我倾向于先使用“”,因为输入速度更快:)
    • 为了我的钱,在更高版本的 PHP 中,没有什么比 HEREDOC 字符串更好的了。
    • @peter 当答案是“没有必要进行基准测试,因为不会有任何现实世界的性能差异”时,您期望什么类型的基准测试? :)
    • @peter 拥有 15 年的经验,过去曾进行过基准测试,为了提供源代码,类似的基准测试,如 this one. 该基准测试必须使用 100,000 次函数调用生成 1MB 以上的数据才能实现数百毫秒的可测量差异。几乎不是现实生活中的情况。即使是最慢的方法(“带有位置参数的“sprintf()”)也需要 0.00456 毫秒,而最快的方法需要 0.00282 毫秒。
    • 您对“现实世界的情况”的看法非常有限。如果您只编写动态网站,您可能确实无法通过这些比较获得真实世界。但是 Stackoverflow 上要求性能差异的人通常不是在做一个基本的网站项目,所以他的“现实世界”可能与你的非常不同。 PHP 可用于处理数百 GB 的数据,因此一毫秒的性能差异可能会导致数小时(或更多)的实际性能差异。我的 PHP 脚本可以处理数周。
    【解决方案2】:

    在所有情况下,第二个都不会更快,因为您提供的是双引号字符串,还必须解析变量。如果要进行微优化,正确的做法是:

    $message = sprintf('The request %s has %d errors', $request, $n);
    

    不过,我相信秒数会变慢(正如@Pekka 指出的差异实际上并不重要),因为函数调用、解析字符串、转换值等的开销。但请注意,2 行代码不等价,因为在第二种情况下 $n 被转换为整数。如果 $n 是“没有错误”,那么第一行将输出:

    The request $request has no error errors
    

    虽然第二个会输出:

    The request $request has 0 errors
    

    【讨论】:

    • 真相是我在源代码中用单引号写的,但是在这里发布问题时我复制/粘贴第一条语句:p 关于转换为整数的好建议 :)
    • 不要相信双引号字符串总是比单引号慢:codeforest.net/…
    • 单引号字符串并不比双引号字符串快。运行时没有区别。
    • @Andrea 参考还是原因?很难相信变量插值(尤其是完成它的解析)没有任何成本(当然,正如许多人指出的那样,它可能不足以影响性能)。
    • @TextGeek 解析不会在运行时发生,它会在文件编译时发生。在编译时有一个微小的、可以忽略不计的差异,但即使是很大的差异也没有关系,因为编译后的操作码会被缓存。
    【解决方案3】:

    here进行了关于“变量扩展与sprintf”的性能分析。

    正如@pekka 所说,“使您的代码对您和其他人来说最易读和维护”。当性能提升“低”(~ 不到两倍)时,忽略它。

    总结基准:PHP 针对双引号和 Heredoc 分辨率进行了优化。尊重平均时间的百分比,仅使用计算很长的字符串,

    • 双引号分辨率:75%
    • heredoc 分辨率:82%
    • 单引号连接:93%
    • sprintf 格式化:117%
    • sprintf 使用索引参数格式化:133%

    请注意,只有 sprintf 执行一些格式化任务(请参阅基准测试的“%s%s%d%s%f%s”),并且正如@Darhazer 所示,它在输出上会有所不同。更好的测试是两个基准测试,一个只比较连接时间('%s' 格式化程序),其他包括格式化过程 - 例如 '%3d%2.2f' 和在将变量扩展为双引号之前的等效功能......还有更多使用短模板字符串的基准组合。

    优点和缺点

    sprintf 的主要优点是,正如基准所显示的,它是非常低成本的格式化程序 (!)。对于通用模板,我建议使用vsprintf 函数。

    双引号(和heredoc)的主要优点是一些性能;和一些标称占位符的可读性和可维护性,与 sprintf 的位置标记相比,随着参数数量(1 之后)的增加而增长。

    索引占位符的使用是 sprintf 可维护性的一半。

    注意:不要使用 single-quote 连接,仅在确实需要时。请记住,PHP 启用安全语法,例如 "Hello {$user}_my_brother!",以及引用,例如 "Hello {$this->name}!"

    【讨论】:

    • 您发布的链接中的每个人都发布了不同意这些结果的 cmets。而且,这些分歧是合乎逻辑的。 Concat 不使用临时变量,字符串插值使用。前者怎么会慢?
    • 嗨@HoldOffHunger,你能解决吗?现在的答案是 Wiki,您可以编辑。
    【解决方案4】:

    我很惊讶,但是对于 PHP 7.*“$variables 替换”是最快的方法

    $message = "The request {$request} has {$n} errors";
    

    您可以简单地自己证明:

    $request = "XYZ";
    $n = "0";
    $mtime = microtime(true);
    for ($i = 0; $i < 1000000; $i++) {
            $message = "The request {$request} has {$n} errors";
    }
    $ctime = microtime(true);
    echo '
    
    "variable $replacement timing": '.  ($ctime-$mtime);
    
    
    
    
    $request = "XYZ";
    $n = "0";
    $mtime = microtime(true);
    for ($i = 0; $i < 1000000; $i++) {
            $message = 'The request '.$request.' has '.$n.' errors';
    }
    $ctime = microtime(true);
    echo '
    
    "concatenation" . $timing: '.  ($ctime-$mtime);
    
    
    
    $request = "XYZ";
    $n = "0";
    $mtime = microtime(true);
    for ($i = 0; $i < 1000000; $i++) {
            $message = sprintf('The request %s has %d errors', $request, $n);
    }
    $ctime = microtime(true);
    echo '
    
    sprintf("%s", $timing): '.  ($ctime-$mtime);
    

    PHP 7.3.5 的结果:

    “可变$更换时间”:0.091434955596924

    “连接”。 $计时:0.11175799369812

    sprintf("%s", $timing): 0.17482495307922

    您可能已经找到诸如“使用 sprintf 而不是双引号中包含的变量,它的速度大约快 10 倍”之类的建议。 What are some good PHP performance tips?

    我明白这是事实,但总有一天。即PHP 5.2.*

    之前

    以下是当时 PHP 5.1.6 的示例:

    “变量$更换时机”:0.67681694030762

    “连接”。 $计时:0.24738907814026

    sprintf("%s", $timing): 0.61580610275269

    【讨论】:

      【解决方案5】:

      对于将多个字符串变量注入一个字符串,第一个会更快。

      $message = "The request $request has $n errors";
      

      对于单次注入,点(.) 连接会更快。

      $message = 'The request '.$request.' has 0 errors';
      

      用十亿循环进行迭代并找出差异。

      例如:

      <?php
      
          $request = "XYZ";
          $n = "0";
          $mtime = microtime(true);
          for ($i = 0; $i < 1000000; $i++) {
                  $message = "The request {$request} has {$n} errors";
          }
          $ctime = microtime(true);
          echo ($ctime-$mtime);
      
      ?>
      

      【讨论】:

      • 由于 CPU 分支预测,循环可能不会显示真正的差异:CPU 可能已经知道会发生什么,因为你一遍又一遍地做同样的事情。
      【解决方案6】:

      最终,在考虑单个变量赋值的上下文时,第一个是最快的,这可以通过查看各种基准来看到。不过,也许使用核心 PHP 函数的 sprintf 风格可以允许更多可扩展的代码,并针对 opcache 或 apc 等字节码级缓存机制进行更好的优化。换句话说,特定大小的应用程序在使用 sprintf 方法时可以使用更少的代码。您需要缓存到 RAM 中的代码越少,用于其他事情或更多脚本的 RAM 就越多。但是,这仅在您的脚本无法使用评估正确放入 RAM 时才重要。

      【讨论】:

        猜你喜欢
        • 2010-09-23
        • 1970-01-01
        • 2011-05-15
        • 2013-04-18
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多