📜 ⬆️ ⬇️

PHP Enchanting arrangement of points above quotes


Regarding PHP micro-optimizations by replacing double quotes with single quotes, there are so many broken copies that it is quite problematic to make a fresh stream. But I'll try.

In this article there will be only one benchmark, where without it, and the main emphasis is placed on the analysis of how it is arranged inside.

Disclaimer


  1. Everything described below is, for the most part, saving on nanoseconds, and in practice it will not give anything but time lost on such a micro-optimization. This is especially true of “optimizations” of compile time.
  2. I will cut the code and output to the maximum, leaving only the essence.
  3. When writing this article I used PHP 7.2.

Necessary introductory


The string in double quotes at the compilation stage is handled a little differently than the string in single quotes.

Single quotes will be resolved like this:
')
statement -> expr -> scalar -> dereferencable_scalar -> T_CONSTANT_ENCAPSED_STRING 

Double so:

 statement -> expr -> scalar -> '"' encaps_list '"' ->        ,  ,     

In the articles on PHP micro-optimizations, there is often advice not to use print , since it is slower than echo . Let's see how they understand.

Parsing echo :

 statement -> T_ECHO echo_expr_list -> echo_expr_list ->  echo_expr -> expr 

Parsing print :

 statement -> expr -> T_PRINT expr -> expr ( ) 

Those. In general, yes, echo is detected a step earlier and this step, it should be noted, is rather heavy.

So that in the course of the article once again do not focus attention, we’ll keep in mind that at the compilation stage, double quotes are lost by single quotes, and print loses echo . Also let's not forget that it is, in the worst case, about nanoseconds.

Well, so as not to get up twice. Here are the diff functions compiling print and echo :

 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 } 

Well, you understand - they are identical in functionality, but print additionally returns a constant equal to 1. I think on this topic with print you can close and forget about it forever.

Simple string, no frills


Strings echo 'Some string'; and echo "Some string"; will be split almost identically into 2 (disklimer p2) tokens.

 T_ECHO: echo T_ENCAPSED_AND_WHITESPACE/T_CONSTANT_ENCAPSED_STRING: "Some string" 

And for single quotes, there will always be T_CONSTANT_ENCAPSED_STRING, and for double quotes, it will always be as. If there is a space in the string, then T_ENCAPSED_AND_WHITESPACE.

Opcodes will be simple to ugliness and absolutely identical:

 line #* EIO op fetch ext return operands ----------------------------------------------------------- 4 0 E > ECHO 'Some string' 


findings


If you want to save a couple of processor cycles at compile time, then, for constant strings, use single quotes.

Dynamic string


There are 4 options.

 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); 

For the first option:

 T_ECHO: echo T_ENCAPSED_AND_WHITESPACE: Hello T_VARIABLE: $name T_ENCAPSED_AND_WHITESPACE: ! Have a nice day! 

For the second (for the third as well, but instead of points there will be commas):

 T_ECHO: echo T_CONSTANT_ENCAPSED_STRING: 'Hello ' string: . T_VARIABLE: $name string: . T_CONSTANT_ENCAPSED_STRING: '! Have a nice day!' 

For the fourth:

 T_STRING: printf T_CONSTANT_ENCAPSED_STRING: 'Hello %s! Have a nice day!' string: , T_VARIABLE: $name 

But with opcodes everything will be much more interesting.

The first:

 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 

Second:

 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 

Third:

 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' 

Fourth:

 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 

Common sense dictates that the option with `printf` will lose in speed to the first three (especially since at the end there is the same ECHO), so we’ll leave it for tasks where you need formatting and we’ll not recall any more in this article.

It would seem that the third option is the fastest - to print successively three lines without concatenations, strange ROPEs, and creating additional variables. But not everything is so simple. The printing function in PHP is of course not Rocket Science, but also by no means a banal C-shny fputs . Who cares - the ball is unraveled starting with php_output_write in the file main / output.c .

CONCAT. Everything is simple - we convert, if necessary, the arguments into strings and create a new zend_string using fast memcpy . The only drawback is that with a long chain of concatenations for each operation, new lines will be created by moving the same baytik from place to place.

But with ROPE_INIT, ROPE_ADD and ROPE_END everything is much more interesting. We follow hands:

  1. ROPE_INIT (ext = 3, return = ~ 3, operands = 'Hello +')
    Allocate the “rope” of three slots (ext), put the string 'Hello +' (operands) in slot 0 and return the temporary variable ~ 3 (return) containing the “rope”.
  2. ROPE_ADD (ext = 1, return = ~ 3, operands = ~ 3,! 0)
    We put in the slot 1 (ext) “rope” ~ 3 (operands) the string 'Vasya' obtained from the variable! 0 (operands) and return the “rope” ~ 3 (return).
  3. ROPE_END (ext = 2, return = ~ 2, operands = ~ 3, '% 21 + Have + a + nice + day% 21')
    We place the line '% 21 + Have + a + nice + day% 21' (operands) in slot 2 (ext), then create a zend_string of the required size and copy all the “rope” slots into it with the same memcpy .

We should also note that in the case of constants and temporary variables, references to the data will be placed in the slots, and there will be no extra copying.

In my opinion, quite elegant. :)

Let's celebrate. Let's take zend_vm_execute.h file as source data ( imkho it will be fair) for 71 thousand lines and print it in different ways in 100 passes, dropping the minimum and maximum (each measurement started 10 times, choosing the most common option):

 <?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; 

What we measureAverage time in seconds
"Rope"0.0129
Multiple echo0.0135
Concatenation0.0158
printf , for completeness0.0245

findings


  1. For strings with simple substitution, suddenly, double quotes are more optimal than single quotes with concatenation. And the longer the lines are used - the greater the gain.
  2. Arguments separated by commas ... There are many nuances. Measurement is faster than concatenation and slower than the “rope”, but too many “variables” associated with I / O.

Conclusion


It is difficult for me to think of a situation when there may be a need for such micro-optimizations. When choosing one or another approach, it is more reasonable to be guided by other principles - for example, readability of the code or the coding style adopted by your company.

As for me personally, I don’t like the approach with concatenations because of the arched view, although in some cases it can be justified.

PS If this kind of analysis is interesting - let me know - there is a lot more there, far from always unambiguous and obvious: an array of VS objects, foreach VS while VS for, your option ... :)

A small explanation of the reading of comments


HEREDOC syntax and “complex strings” (where the variables in curly brackets are inside) are the same strings in double quotes and are compiled in exactly the same way.

PHP jumble with HTML like this:
 <?php $name = 'Vasya';?>Hello <?=$name?>! Have a nice day! 

It's just 3 echo in a row.

Source: https://habr.com/ru/post/447416/


All Articles