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

王林
リリース: 2024-08-16 16:34:49
オリジナル
392 人が閲覧しました

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

「すべてはすでに言われていますが、まだ誰もが言っていません」 – Karl Valentin

この精神で、ニキータ・ポポフがすでに12年前に書いたのと同じテーマについて記事を書いています(彼の記事を読んでいる場合は、ここで読むのをやめてください)

ファズとは一体何でしょうか?

PHP は文字列補間を実行し、文字列内で使用されている変数を検索し、使用されている変数の値に置き換えます。

リーリー

この機能は、二重引用符で囲まれた文字列およびヒアドキュメントに限定されています。一重引用符 (または nowdoc) を使用すると、別の結果が得られます:

リーリー

これを見てください: PHP はその一重引用符で囲まれた文字列内の変数を検索しません。したがって、どこでも一重引用符を使用し始めることができます。そこで人々はこのような変更を提案し始めました ..

リーリー

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

事件は解決しましたか?

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

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

レクサー

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

リーリー

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

パーサー

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

リーリー

これも試して、他のコードの 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 の値は最適化後にオペコードを出力します
リーリー

仮説

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

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

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

二重引用符:


リーリー リーリー

vs.一重引用符:


リーリー リーリー

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

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

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

二重引用符:


リーリー

vs.一重引用符:


リーリー

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

新しい仮説

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

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


リーリー

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


T_OPEN_TAG (
        
ログイン後にコピー

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:


        
ログイン後にコピー
  • 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 サイトの他の関連記事を参照してください。

ソース:dev.to
このウェブサイトの声明
この記事の内容はネチズンが自主的に寄稿したものであり、著作権は原著者に帰属します。このサイトは、それに相当する法的責任を負いません。盗作または侵害の疑いのあるコンテンツを見つけた場合は、admin@php.cn までご連絡ください。
人気のチュートリアル
詳細>
最新のダウンロード
詳細>
ウェブエフェクト
公式サイト
サイト素材
フロントエンドテンプレート
私たちについて 免責事項 Sitemap
PHP中国語ウェブサイト:福祉オンライン PHP トレーニング,PHP 学習者の迅速な成長を支援します!