PHPのマイクロ最適化に関しては、二重引用符を単一引用符に置き換えることにより、多くのコピーが破損するため、新しいストリームを作成することは非常に問題になります。 しかし、私はしようとします。
この記事にはベンチマークが1つしかありませんが、それがなければどこになりますか。そして、それがどのように配置されているかを分析することに主な重点が置かれます。
免責事項
- 以下で説明するすべては、ほとんどの場合、ナノ秒の節約であり、実際には、このような最適化で失われた時間を失うだけです。 これは、コンパイル時間の「最適化」に特に当てはまります。
- コードと出力を最大限にカットし、エッセンスのみを残します。
- 記事を書くとき、PHP 7.2を使用しました
必要な入門
コンパイル段階での二重引用符で囲まれた文字列は、単一引用符で囲まれた文字列とはわずかに異なる方法で処理されます。
単一引用符は次のように解析されます。
statement -> expr -> scalar -> dereferencable_scalar -> T_CONSTANT_ENCAPSED_STRING
ダブルそう:
statement -> expr -> scalar -> '"' encaps_list '"' -> , ,
PHPのマイクロ最適化に関する記事では、 echoよりも遅いため、 printを使用しないことをお勧めします。 それらがどのようにソートされるかを見てみましょう。
エコーの解析:
statement -> T_ECHO echo_expr_list -> echo_expr_list -> echo_expr -> expr
印刷の解析:
statement -> expr -> T_PRINT expr -> expr ( )
つまり 一般に、はい、 エコーは 1つ前の段階で検出されますが、この手順は非常に難しいことに注意してください。
記事中に再び注意を喚起しないために、コンパイル段階では二重引用符は単一を失い、 印刷はechoを失います。 また、最悪の場合、ナノ秒程度であることを忘れないでください。
まあ、二度起きないように。 printとechoをコンパイルするdiff関数は次のとおりです。
1 - void zend_compile_print(znode *result, zend_ast *ast) /* {{{ */ 1 + void zend_compile_echo(zend_ast *ast) /* {{{ */ 2 2 { 3 3 zend_op *opline; 4 4 zend_ast *expr_ast = ast->child[0]; 5 5 6 6 znode expr_node; 7 7 zend_compile_expr(&expr_node, expr_ast); 8 8 9 9 opline = zend_emit_op(NULL, ZEND_ECHO, &expr_node, NULL); 10 - opline->extended_value = 1; 11 - 12 - result->op_type = IS_CONST; 13 - ZVAL_LONG(&result->u.constant, 1); 10 + opline->extended_value = 0; 14 11 }
さて、あなたは理解しています-それらは機能的には同じですが、 printはさらに1に等しい定数を返します。printに関するこのトピックでは、あなたはそれを閉じて永遠に忘れることができると思います。
シンプルなライン、フリルなし
echo 'Some string';
echo "Some string";
を
echo "Some string";
ほぼ同じように2(免責事項P2)トークンに分割されます。
T_ECHO: echo T_ENCAPSED_AND_WHITESPACE/T_CONSTANT_ENCAPSED_STRING: "Some string"
さらに、単一引用符の場合は常にT_CONSTANT_ENCAPSED_STRINGが存在し、二重引用符の場合は常に存在します。 行にスペースがある場合、T_ENCAPSED_AND_WHITESPACE。
オペコードは簡単に不名誉になり、まったく同じになります。
line #* EIO op fetch ext return operands ----------------------------------------------------------- 4 0 E > ECHO 'Some string'
結論
コンパイル段階でプロセッササイクルを数回保存する場合は、定数文字列には一重引用符を使用します。
ダイナミックライン
4つのオプションがあります。
echo "Hello $name! Have a nice day!"; echo 'Hello '.$name.'! Have a nice day!'; echo 'Hello ', $name, '! Have a nice day!'; printf ('Hello %s! Have a nice day!', $name);
最初のオプションの場合:
T_ECHO: echo T_ENCAPSED_AND_WHITESPACE: Hello T_VARIABLE: $name T_ENCAPSED_AND_WHITESPACE: ! Have a nice day!
2番目の場合(3番目の場合も、ピリオドの代わりにコンマがあります):
T_ECHO: echo T_CONSTANT_ENCAPSED_STRING: 'Hello ' string: . T_VARIABLE: $name string: . T_CONSTANT_ENCAPSED_STRING: '! Have a nice day!'
4番目の場合:
T_STRING: printf T_CONSTANT_ENCAPSED_STRING: 'Hello %s! Have a nice day!' string: , T_VARIABLE: $name
しかし、オペコードを使用すると、すべてがはるかに楽しくなります。
最初のもの:
echo "Hello $name! Have a nice day!"; line #* EIO op fetch ext return operands ----------------------------------------------------------- 3 0 E > ASSIGN !0, 'Vasya' 4 1 ROPE_INIT 3 ~3 'Hello+' 2 ROPE_ADD 1 ~3 ~3, !0 3 ROPE_END 2 ~2 ~3, '%21+Have+a+nice+day%21' 4 ECHO ~2
第二:
echo 'Hello '.$name.'! Have a nice day!'; line #* EIO op fetch ext return operands ----------------------------------------------------------- 3 0 E > ASSIGN !0, 'Vasya' 4 1 CONCAT ~2 'Hello+', !0 2 CONCAT ~3 ~2, '%21+Have+a+nice+day%21' 3 ECHO ~3
第三:
echo 'Hello ', $name, '! Have a nice day!'; line #* EIO op fetch ext return operands ----------------------------------------------------------- 3 0 E > ASSIGN !0, 'Vasya' 4 1 ECHO 'Hello+' 2 ECHO !0 3 ECHO '%21+Have+a+nice+day%21'
4番目:
printf ('Hello %s! Have a nice day!', $name); line #* EIO op fetch ext return operands ----------------------------------------------------------- 3 0 E > ASSIGN !0, 'Vasya' 4 1 INIT_FCALL 'printf' 2 SEND_VAL 'Hello+%25s%21+Have+a+nice+day%21' 3 SEND_VAR !0 4 DO_ICALL
常識では、 `printf`のオプションは最初の3つで速度が低下することを示しています(特に最終的にはまだ同じECHOであるため)。したがって、書式設定が必要なタスクにそれを残し、この記事ではこれ以上覚えません。
3番目のオプションは、連結、奇妙なROPE、および追加の変数の作成なしで連続して3行を印刷するのが最も速いようです。 しかし、それほど単純ではありません。 PHPの印刷関数は確かにRocket Scienceではありませんが、決して平凡なC-shny fputsではありません。 誰が気にしている -ボールは、 main / output.cファイルのphp_output_writeで始まります 。
CONCAT。 ここではすべてが簡単です-必要に応じて、引数を文字列に変換し、高速のmemcpyを使用して新しいzend_stringを作成します。 唯一のマイナスは、各操作の連結の長いチェーンで、場所から場所に同じバイトをシフトすることによって新しい行が作成されることです。
しかし、ROPE_INIT、ROPE_ADD、およびROPE_ENDを使用すると、すべてがはるかに興味深いものになります。 手に従ってください:
- ROPE_INIT(ext = 3、return =〜3、operands = 'Hello +')
3つのスロット(ext)から「ロープ」を割り当て、文字列「Hello +」(オペランド)をスロット0に入れ、「ロープ」を含む一時変数〜3(戻り)を返します。 - ROPE_ADD(ext = 1、return =〜3、operands =〜3、0)
「ロープ」のスロット1(ext)〜3(オペランド)に変数から取得した文字列「Vasya」を入れます!0(オペランド)を返し、「ロープ」〜3(return)を返します。 - ROPE_END(ext = 2、return =〜2、operands =〜3、 '%21 + Have + a + nice + day%21')
行 '%21 + Have + a + nice + day%21'(オペランド)をスロット2(ext)に入れた後、必要なサイズのzend_stringを作成し、同じmemcpyですべての「ロープ」スロットを順番にコピーします。
それとは別に、定数と一時変数の場合、データへのリンクがスロットに配置され、不必要なコピーがないことに注意する価値があります。
私の意見では、かなりエレガントです。 :)
ベンチマークしましょう。 ソースデータとして、ファイル7endsのzend_vm_execute.h (これは本当です)を取得し、100パスで100通りに印刷し、最小値と最大値をドロップします(各測定は10回開始され、最も一般的なオプションを選択します):
<?php $file = explode("\n", file_get_contents("C:\projects\C\php-src\Zend\zend_vm_execute.h")); $out = []; for ($c = 0; $c < 100; $c++) { $start = microtime(true); ob_start(); $i = 0; foreach ($file as $line) { $i++; // echo 'line: ', $i, 'text: ', $line; // echo 'line: ' . $i . 'text: ' . $line; // echo "line: $i text: $line"; // printf('line: %d text: %s', $i, $line); } ob_end_clean(); $out[] = (microtime(true) - $start); } $min = min($out); $max = max($out); echo (array_sum($out) - $min - $max) / 98;
測定するもの | 秒単位の平均時間 |
---|---|
「ロープ」 | 0.0129 |
いくつかのエコー | 0.0135 |
連結 | 0.0158 |
完全性のためのprintf | 0.0245 |
結論
- 単純な置換を含む文字列の場合、突然の二重引用符はすべて、連結を含む単一引用符よりも最適です。 また、ラインが長く使用されるほど、ゲインが大きくなります。
- コンマで区切られた引数...多くのニュアンスがあります。 測定では、連結よりも速く、「ロープ」よりも遅くなりますが、入力/出力に関連する「変数」が多すぎます。
おわりに
このような最適化の必要性が生じる可能性がある状況を思い付くのは困難です。 このアプローチまたはそのアプローチを選択するときは、他の原則(たとえば、コードの可読性や会社で採用されているコーディングスタイルなど)に導かれる方が合理的です。
個人的には、vyrviglaznyの外観のために連結アプローチが好きではありませんが、正当化できる場合もあります。
PSこの種の分析が興味深い場合-私に知らせてください-常に明確で明白なものから遠く離れて、他の多くのものがあります:VSオブジェクトの配列、VS対VS、あなたのオプション... :)
コメントを読んで少し説明
HEREDOC構文と「複雑な文字列」(変数が中括弧で囲まれている)は、二重引用符で囲まれた同じ文字列であり、まったく同じ方法でコンパイルされます。
PHPと次のようなHTMLの組み合わせ:
<?php $name = 'Vasya';?>Hello <?=$name?>! Have a nice day!
これは、連続した3つのエコーです。