ホームページ  >  記事  >  バックエンド開発  >  二重引用符が多すぎるかどうか、それが問題です。

二重引用符が多すぎるかどうか、それが問題です。

王林
王林オリジナル
2024-08-16 16:34:49500ブラウズ

つい最近、PHP 関係者が依然として一重引用符と二重引用符について話しており、一重引用符の使用は単なる微細な最適化であるが、常に一重引用符を使用することに慣れていれば、大量の CPU を節約できるということを再び聞きました。サイクル!

「すべてはすでに言われていますが、まだ誰もが言っていません」 – カール・ヴァレンティン

私はこの精神に基づいて、ニキータ・ポポフがすでに 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';

.. なぜなら、PHP は一重引用符で囲まれた文字列 (この例には存在しません) 内の変数を検索しないため、より高速になり、そのコードを実行するたびに大量の CPU サイクルを節約できるからです。全員が幸せになり、事件は解決しました。

事件は解決しましたか?

一重引用符の使用と二重引用符の使用には明らかに違いがありますが、何が起こっているのかを理解するには、もう少し深く掘り下げる必要があります。

PHP はインタープリター型言語ですが、仮想マシンが実際に実行できるもの (オペコード) を取得するために特定の部分が連携するコンパイル ステップを使用しています。では、PHP ソースコードからオペコードにどのようにしてアクセスできるのでしょうか?

レクサー

レクサーはソース コード ファイルをスキャンし、トークンに分割します。これが何を意味するのかについての簡単な例は、token_get_all() 関数のドキュメントに記載されています。

T_OPEN_TAG (<?php )
T_ECHO (echo)
T_WHITESPACE ( )
T_CONSTANT_ENCAPSED_STRING ("")

この 3v4l.org スニペットで実際の動作を確認し、試してみることができます。

パーサー

パーサーはこれらのトークンを受け取り、そこから抽象構文ツリーを生成します。上記の例の AST 表現を JSON として表すと次のようになります。

{
  "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 を設定して有効にしている場合 (これを強くお勧めします)、OPcache に保存されるものでもあります。

オペコードを表示するには、複数のオプションがあります (おそらくもっとあるかもしれませんが、私はこれら 3 つを知っています)。

  1. vulcan ロジック ダンパー拡張機能を使用します。 3v4l.org にも焼き付けられています
  2. phpdbg -p script.php を使用してオペコードをダンプします
  3. または、OPcache の opcache.opt_debug_level INI 設定を使用して、オペコードを出力するようにします。
    • 値 0x10000 は、最適化前のオペコードを出力します
    • 値 0x20000 は、最適化後にオペコードを出力します
$ echo '<?php echo "";' > foo.php
$ php -dopcache.opt_debug_level=0x10000 foo.php
$_main:
...
0000 ECHO string("")
0001 RETURN int(1)




</p>
<h2>
  
  
  仮説
</h2>

<p>一重引用符と二重引用符を使用するときに CPU サイクルを節約するという最初のアイデアに戻ると、これが成り立つのは、PHP が単一のリクエストごとに実行時にこれらの文字列を評価する場合に限られるということに誰もが同意すると思います。</p>

<h2>
  
  
  実行時に何が起こるのでしょうか?
</h2>

<p>それでは、PHP が 2 つの異なるバージョンに対してどのオペコードを作成するかを見てみましょう。</p>

<p>二重引用符:<br>
</p>

<pre class="brush:php;toolbar:false"><?php echo "apple";
0000 ECHO string("apple")
0001 RETURN int(1)

vs.一重引用符:

<?php echo 'apple';
0000 ECHO string("apple")
0001 RETURN int(1)

ちょっと待って、何か奇妙なことが起こりました。これは同じに見えます!私のマイクロ最適化はどこへ行ったのでしょうか?

そうですね、おそらく、ECHO オペコード ハンドラーの実装は指定された文字列を解析しますが、そうするように指示するマーカーやその他のものはありません...うーん?

別のアプローチを試して、これら 2 つのケースに対してレクサーが何を行うかを見てみましょう:

二重引用符:

T_OPEN_TAG (<?php )
T_ECHO (echo)
T_WHITESPACE ( )
T_CONSTANT_ENCAPSED_STRING ("")

vs.一重引用符:

Line 1: T_OPEN_TAG (<?php )
Line 1: T_ECHO (echo)
Line 1: T_WHITESPACE ( )
Line 1: T_CONSTANT_ENCAPSED_STRING ('')

トークンは依然として二重引用符と一重引用符を区別していますが、AST をチェックすると、両方のケースで同じ結果が得られます。唯一の違いは、Scalar_String ノード属性の rawValue であり、一重引用符と二重引用符がまだ含まれていますが、どちらの場合も、値には二重引用符が使用されます。

新しい仮説

文字列補間は実際にはコンパイル時に行われる可能性はありますか?

もう少し「洗練された」例で確認してみましょう:

<?php
$juice="apple";
echo "juice: $juice";

このファイルのトークンは次のとおりです:

T_OPEN_TAG (<?php)
T_VARIABLE ($juice)
T_CONSTANT_ENCAPSED_STRING ("apple")
T_WHITESPACE ()
T_ECHO (echo)
T_WHITESPACE ( )
T_ENCAPSED_AND_WHITESPACE (juice: )
T_VARIABLE ($juice)

Look at the last two tokens! String interpolation is handled in the lexer and as such is a compile time thing and has nothing to do with runtime.

Too double quote or not, that

For completeness, let's have a look at the opcodes generated by this (after optimisation, using 0x20000):

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

This is different opcode than we had in our simple

Get to the point: should I concat or interpolate?

Let's have a look at these three different versions:

<?php
$juice = "apple";
echo "juice: $juice $juice";
echo "juice: ", $juice, " ", $juice;
echo "juice: ".$juice." ".$juice;
  • the first version is using string interpolation
  • the second is using a comma separation (which AFAIK only works with echo and not with assigning variables or anything else)
  • and the third option uses string concatenation

The first opcode assigns the string "apple" to the variable $juice:

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

The first version (string interpolation) is using a rope as the underlying data structure, which is optimised to do as little string copies as possible.

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

The second version is the most memory effective as it does not create an intermediate string representation. Instead it does multiple calls to ECHO which is a blocking call from an I/O perspective so depending on your use case this might be a downside.

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

The third version uses CONCAT/FAST_CONCAT to create an intermediate string representation and as such might use more memory than the rope version.

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

So ... what is the right thing to do here and why is it string interpolation?

String interpolation uses either a FAST_CONCAT in the case of echo "juice: $juice"; or highly optimised ROPE_* opcodes in the case of echo "juice: $juice $juice";, but most important it communicates the intent clearly and none of this has been bottle neck in any of the PHP applications I have worked with so far, so none of this actually matters.

TLDR

String interpolation is a compile time thing. Granted, without OPcache the lexer will have to check for variables used in double quoted strings on every request, even if there aren't any, waisting CPU cycles, but honestly: The problem is not the double quoted strings, but not using OPcache!

However, there is one caveat: PHP up to 4 (and I believe even including 5.0 and maybe even 5.1, I don't know) did string interpolation at runtime, so using these versions ... hmm, I guess if anyone really still uses PHP 5, the same as above applies: The problem is not the double quoted strings, but the use of an outdated PHP version.

Final advice

Update to the latest PHP version, enable OPcache and live happily ever after!

以上が二重引用符が多すぎるかどうか、それが問題です。の詳細内容です。詳細については、PHP 中国語 Web サイトの他の関連記事を参照してください。

声明:
この記事の内容はネチズンが自主的に寄稿したものであり、著作権は原著者に帰属します。このサイトは、それに相当する法的責任を負いません。盗作または侵害の疑いのあるコンテンツを見つけた場合は、admin@php.cn までご連絡ください。