Rumah  >  Artikel  >  pembangunan bahagian belakang  >  Petikan yang terlalu berganda atau tidak, itulah persoalannya!

Petikan yang terlalu berganda atau tidak, itulah persoalannya!

王林
王林asal
2024-08-16 16:34:49512semak imbas

Baru-baru ini saya mendengar lagi bahawa orang PHP masih bercakap tentang petikan tunggal berbanding petikan berganda dan bahawa menggunakan petikan tunggal hanyalah pengoptimuman mikro tetapi jika anda terbiasa menggunakan petikan tunggal sepanjang masa anda akan menjimatkan banyak CPU kitaran!

"Semuanya telah dikatakan, tetapi belum oleh semua orang" – Karl Valentin

Dengan semangat inilah saya menulis artikel tentang topik yang sama yang dilakukan Nikita Popov 12 tahun yang lalu (jika anda membaca artikelnya, anda boleh berhenti membaca di sini).

Apakah yang dimaksudkan dengan fuzz?

PHP melakukan interpolasi rentetan, di mana ia mencari penggunaan pembolehubah dalam rentetan dan menggantikannya dengan nilai pembolehubah yang digunakan:

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

Ciri ini terhad kepada rentetan dalam petikan berganda dan heredoc. Menggunakan petikan tunggal (atau nowdoc) akan menghasilkan hasil yang berbeza:

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

Lihat itu: PHP tidak akan mencari pembolehubah dalam rentetan petikan tunggal itu. Jadi kita boleh mula menggunakan petikan tunggal di mana-mana sahaja. Jadi orang mula mencadangkan perubahan seperti ini..

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

.. kerana ia akan menjadi lebih pantas dan ia akan menjimatkan sekumpulan kitaran CPU dengan setiap pelaksanaan kod itu kerana PHP tidak mencari pembolehubah dalam rentetan petikan tunggal (yang tidak wujud dalam contoh pula) dan semua orang gembira, kes ditutup.

Kes ditutup?

Jelas sekali terdapat perbezaan dalam menggunakan petikan tunggal berbanding petikan berganda, tetapi untuk memahami perkara yang sedang berlaku, kita perlu menggali lebih mendalam.

Walaupun PHP ialah bahasa yang ditafsirkan, ia menggunakan langkah penyusunan di mana bahagian tertentu bermain bersama untuk mendapatkan sesuatu yang sebenarnya boleh dilaksanakan oleh mesin maya, iaitu opcode. Jadi bagaimana kita boleh mendapatkan daripada kod sumber PHP kepada kod op?

Lexer itu

Lexer mengimbas fail kod sumber dan memecahkannya kepada token. Contoh ringkas tentang maksud ini boleh didapati dalam dokumentasi fungsi token_get_all(). Kod sumber PHP hanya

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

Kita boleh melihat tindakan ini dan bermain dengannya dalam coretan 3v4l.org ini.

Penghurai

Penghurai mengambil token ini dan menjana pepohon sintaks abstrak daripadanya. Perwakilan AST bagi contoh di atas kelihatan seperti ini apabila diwakili sebagai 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": ""
        }
      ]
    }
  ]
}

Sekiranya anda mahu bermain dengan ini juga dan melihat bagaimana rupa AST untuk kod lain, saya dapati https://phpast.com/ oleh Ryan Chandler dan https://php-ast-viewer.com/ yang kedua-duanya menunjukkan kepada anda AST bagi sekeping kod PHP yang diberikan.

Penyusun

Pengkompil mengambil AST dan mencipta opcode. Opcode ialah perkara yang dilaksanakan oleh mesin maya, ia juga akan disimpan dalam OPcache jika anda mempunyai persediaan dan didayakan itu (yang saya sangat mengesyorkan).

Untuk melihat opcode, kami mempunyai berbilang pilihan (mungkin lebih banyak, tetapi saya tahu tiga ini):

  1. gunakan sambungan dumper logik vulcan. Ia juga dipanggang ke dalam 3v4l.org
  2. gunakan phpdbg -p script.php untuk membuang opcode
  3. atau gunakan tetapan INI opcache.opt_debug_level untuk OPcache untuk menjadikannya mencetak opcode
    • nilai 0x10000 output opcode sebelum pengoptimuman
    • nilai 0x20000 output opcode selepas pengoptimuman
$ echo '<?php echo "";' > foo.php
$ php -dopcache.opt_debug_level=0x10000 foo.php
$_main:
...
0000 ECHO string("")
0001 RETURN int(1)




</p>
<h2>
  
  
  Hipotesis
</h2>

<p>Berbalik kepada idea awal untuk menyimpan kitaran CPU apabila menggunakan petikan tunggal berbanding petikan berganda, saya rasa kita semua bersetuju bahawa ini hanya akan benar jika PHP akan menilai rentetan ini pada masa jalan untuk setiap permintaan tunggal.</p>

<h2>
  
  
  Apa yang berlaku pada masa jalan?
</h2>

<p>Jadi mari kita lihat opcode yang dibuat oleh PHP untuk dua versi berbeza.</p>

<p>Petikan berganda:<br>
</p>

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

lwn. petikan tunggal:

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

Hei tunggu, sesuatu yang pelik berlaku. Ini kelihatan sama! Ke manakah perginya pengoptimuman mikro saya?

Baiklah mungkin, hanya mungkin pelaksanaan pengendali opcode ECHO menghuraikan rentetan yang diberikan, walaupun tiada penanda atau sesuatu yang lain yang menyuruhnya berbuat demikian ... hmm ?

Mari cuba pendekatan yang berbeza dan lihat apa yang dilakukan oleh lexer untuk dua kes tersebut:

Petikan berganda:

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

lwn. petikan tunggal:

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

Token masih membezakan antara petikan berganda dan tunggal, tetapi menyemak AST akan memberi kita hasil yang sama untuk kedua-dua kes - satu-satunya perbezaan ialah rawValue dalam atribut nod Scalar_String, yang masih mempunyai petikan tunggal/ganda, tetapi nilai menggunakan petikan berganda dalam kedua-dua kes.

Hipotesis Baru

Mungkinkah, interpolasi rentetan itu sebenarnya dilakukan pada masa penyusunan?

Mari kita semak dengan contoh yang lebih "canggih":

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

Token untuk fail ini ialah:

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!

Atas ialah kandungan terperinci Petikan yang terlalu berganda atau tidak, itulah persoalannya!. Untuk maklumat lanjut, sila ikut artikel berkaitan lain di laman web China PHP!

Kenyataan:
Kandungan artikel ini disumbangkan secara sukarela oleh netizen, dan hak cipta adalah milik pengarang asal. Laman web ini tidak memikul tanggungjawab undang-undang yang sepadan. Jika anda menemui sebarang kandungan yang disyaki plagiarisme atau pelanggaran, sila hubungi admin@php.cn