」工欲善其事,必先利其器。「—孔子《論語.錄靈公》
首頁 > 程式設計 > 雙引號是否過多,這就是問題所在!

雙引號是否過多,這就是問題所在!

發佈於2024-08-27
瀏覽:398

最近我又听说 PHP 人们仍然在谈论单引号与双引号,并且使用单引号只是一种微观优化,但如果你习惯一直使用单引号,你会节省大量的 CPU循环!

“一切都已经说过了,但还不是每个人都说的” – Karl Valentin

正是本着这种精神,我正在写一篇关于 Nikita Popov 12 年前已经做过的同一主题的文章(如果您正在阅读他的文章,您可以在这里停止阅读)。

毛茸茸的到底是什么?

PHP 执行字符串插值,在字符串中搜索变量的使用情况,并将其替换为所使用变量的值:

$juice = "apple";
echo "They drank some $juice juice.";
// will output: They drank some apple juice.

此功能仅限于双引号和定界符中的字符串。使用单引号(或 nowdoc)将产生不同的结果:

$juice = "apple";
echo 'They drank some $juice juice.';
// will output: They drank some $juice juice.

请注意:PHP 不会搜索该单引号字符串中的变量。所以我们可以开始在任何地方使用单引号。所以人们开始建议这样的改变..

- $juice = "apple";
  $juice = 'apple';

.. 因为它会更快,并且每次执行该代码都会节省大量 CPU 周期,因为 PHP 不会在单引号字符串中查找变量(无论如何,该示例中不存在这些变量)并且皆大欢喜,案件结案。

案件结案了吗?

显然,使用单引号和双引号是有区别的,但为了理解发生了什么,我们需要更深入地挖掘。

尽管 PHP 是一种解释性语言,但它使用编译步骤,其中某些部分一起运行以获得虚拟机实际可以执行的内容,即操作码。那么我们如何从 PHP 源代码获取操作码呢?

词法分析器

词法分析器扫描源代码文件并将其分解为标记。可以在 token_get_all() 函数文档中找到该含义的简单示例。一个 PHP 源代码只是

T_OPEN_TAG (



我们可以在这个 3v4l.org 代码片段中看到它的实际效果并使用它。

解析器

解析器获取这些标记并从中生成抽象语法树。当表示为 JSON 时,上述示例的 AST 表示如下所示:

{
  "data": [
    {
      "nodeType": "Stmt_Echo",
      "attributes": {
        "startLine": 1,
        "startTokenPos": 1,
        "startFilePos": 6,
        "endLine": 1,
        "endTokenPos": 4,
        "endFilePos": 13
      },
      "exprs": [
        {
          "nodeType": "Scalar_String",
          "attributes": {
            "startLine": 1,
            "startTokenPos": 3,
            "startFilePos": 11,
            "endLine": 1,
            "endTokenPos": 3,
            "endFilePos": 12,
            "kind": 2,
            "rawValue": "\"\""
          },
          "value": ""
        }
      ]
    }
  ]
}

如果你也想玩这个,看看其他代码的 AST 是什么样子,我找到了 Ryan Chandler 的 https://phpast.com/ 和 https://php-ast-viewer.com/ ,其中两者都显示给定 PHP 代码片段的 AST。

编译器

编译器采用 AST 并创建操作码。操作码是虚拟机执行的内容,如果您进行了设置并启用了它,它也会存储在 OPcache 中(我强烈推荐)。

要查看操作码,我们有多个选项(也许更多,但我确实知道这三个):

  1. 使用 vulcan 逻辑转储器扩展。它也被纳入 3v4l.org
  2. 使用 phpdbg -p script.php 转储操作码
  3. 或者使用 OPcache 的 opcache.opt_debug_level INI 设置使其打印出操作码
    • 优化前0x10000的值输出操作码
    • 0x20000的值输出优化后的操作码
$ echo ' foo.php
$ php -dopcache.opt_debug_level=0x10000 foo.php
$_main:
...
0000 ECHO string("")
0001 RETURN int(1)

假设

回到使用单引号与双引号时节省 CPU 周期的最初想法,我想我们都同意,只有当 PHP 在运行时为每个请求评估这些字符串时,这才是正确的。

运行时会发生什么?

那么让我们看看 PHP 为两个不同版本创建了哪些操作码。

双引号:







0000 ECHO string("apple")
0001 RETURN int(1)

对比。单引号:







0000 ECHO string("apple")
0001 RETURN int(1)

嘿等等,奇怪的事情发生了。这看起来一模一样!我的微优化去哪儿了?

好吧,也许 ECHO 操作码处理程序的实现会解析给定的字符串,尽管没有标记或其他东西告诉它这样做......嗯?

让我们尝试不同的方法,看看词法分析器对这两种情况做了什么:

双引号:

T_OPEN_TAG (



对比。单引号:

Line 1: T_OPEN_TAG (



标记仍然区分双引号和单引号,但是检查 AST 将为我们提供两种情况相同的结果 - 唯一的区别是 Scalar_String 节点属性中的 rawValue,它仍然具有单/双引号,但是该值在两种情况下都使用双引号。

新假设

难道字符串插值实际上是在编译时完成的吗?

让我们看一个稍微“复杂”的例子:





此文件的标记是:

T_OPEN_TAG (



看看最后两个标记!字符串插值在词法分析器中处理,因此是编译时的事情,与运行时无关。

Too double quote or not, that

为了完整起见,让我们看一下由此生成的操作码(优化后,使用0x20000):

0000 ASSIGN CV0($juice) string("apple")
0001 T2 = FAST_CONCAT string("juice: ") CV0($juice)
0002 ECHO T2
0003 RETURN int(1)

这与我们简单的

进入正题:我应该连接还是插值?

让我们看看这三个不同的版本:





  • 第一个版本使用字符串插值
  • 第二个是使用逗号分隔(据我所知,它仅适用于 echo,不适用于分配变量或其他任何内容)
  • 第三个选项使用字符串连接

第一个操作码将字符串“apple”分配给变量 $juice:

0000 ASSIGN CV0($juice) string("apple")

第一个版本(字符串插值)使用绳索作为底层数据结构,经过优化以尽可能少地复制字符串。

0001 T2 = ROPE_INIT 4 string("juice: ")
0002 T2 = ROPE_ADD 1 T2 CV0($juice)
0003 T2 = ROPE_ADD 2 T2 string(" ")
0004 T1 = ROPE_END 3 T2 CV0($juice)
0005 ECHO T1

第二个版本是最有效的内存,因为它不创建中间字符串表示形式。相反,它会多次调用 ECHO,从 I/O 角度来看,这是一个阻塞调用,因此根据您的用例,这可能是一个缺点。

0006 ECHO string("juice: ")
0007 ECHO CV0($juice)
0008 ECHO string(" ")
0009 ECHO CV0($juice)

第三个版本使用 CONCAT/FAST_CONCAT 创建中间字符串表示形式,因此可能比绳索版本使用更多的内存。

0010 T1 = CONCAT string("juice: ") CV0($juice)
0011 T2 = FAST_CONCAT T1 string(" ")
0012 T1 = CONCAT T2 CV0($juice)
0013 ECHO T1

那么...这里应该做什么以及为什么是字符串插值?

字符串插值在 echo "juice: $juice" 的情况下使用 FAST_CONCAT;或在 echo "juice: $juice $juice"; 的情况下高度优化的 ROPE_* 操作码;但最重要的是它清楚地传达了意图,并且这些都不是我迄今为止使用过的任何 PHP 应用程序的瓶颈,所以这些实际上都不重要。

总长DR

字符串插值是编译时的事情。诚然,如果没有 OPcache,词法分析器将必须在每个请求上检查双引号字符串中使用的变量,即使没有任何变量,也会浪费 CPU 周期,但说实话:问题不在于双引号字符串,而在于不使用 OPcache!

但是,有一个警告:PHP 最高 4(我相信甚至包括 5.0,甚至可能是 5.1,我不知道)在运行时进行了字符串插值,所以使用这些版本......嗯,我想如果确实有人仍在使用 PHP 5,与上面的情况相同:问题不是双引号字符串,而是使用过时的 PHP 版本。

最终建议

更新PHP最新版本,启用OPcache,从此幸福生活!

版本聲明 本文轉載於:https://dev.to/realflowcontrol/too-double-quote-or-not-thats-the-question-78l?1如有侵犯,請聯絡[email protected]刪除
最新教學 更多>

免責聲明: 提供的所有資源部分來自互聯網,如果有侵犯您的版權或其他權益,請說明詳細緣由並提供版權或權益證明然後發到郵箱:[email protected] 我們會在第一時間內為您處理。

Copyright© 2022 湘ICP备2022001581号-3