PHP Encantando pontos sobre aspas


Quanto às micro otimizações do PHP, substituindo aspas duplas por aspas simples, há tantas cópias quebradas que é bastante problemático criar um novo fluxo. Mas vou tentar.

Neste artigo, haverá apenas uma referência, onde estaria sem ela, e a ênfase principal está na análise de como ela é organizada.

Isenção de responsabilidade


  1. Tudo descrito abaixo é, na maioria das vezes, economizando nanossegundos e, na prática, não fará nada além de perder o tempo perdido com essa microoptimização. Isto é especialmente verdade para "otimizações" do tempo de compilação.
  2. Cortarei o código e a saída ao máximo, deixando apenas a essência.
  3. Ao escrever um artigo, usei o PHP 7.2

Introdutório necessário


Uma cadeia de caracteres entre aspas duplas no estágio de compilação é processada de maneira ligeiramente diferente de uma cadeia de caracteres entre aspas simples.

Aspas simples serão analisadas da seguinte maneira:

statement -> expr -> scalar -> dereferencable_scalar -> T_CONSTANT_ENCAPSED_STRING 

Dobro assim:

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

Em artigos sobre micro otimizações do PHP, muitas vezes há conselhos para não usar a impressão , pois é mais lenta que o eco . Vamos ver como eles são classificados.

Analisando eco :

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

Análise de impressão :

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

I.e. em geral, sim, o eco é detectado um passo mais cedo e esse passo, deve-se notar, é bastante difícil.

Para não acentuar a atenção novamente durante o artigo, teremos em mente que, no estágio de compilação, as aspas duplas perdem um único e a impressão perde o eco . Além disso, não devemos esquecer que, na pior das hipóteses, trata-se de nanossegundos.

Bem, para não se levantar duas vezes. Aqui estão as funções diff, compilando print e 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 } 

Bem, você entende - eles são idênticos em funcionalidade, mas a impressão também retorna uma constante igual a 1. Penso neste tópico com a impressão, você pode fechar e esquecê-lo para sempre.

Linha simples, sem frescuras


Strings echo 'Some string'; e echo "Some string"; será dividido quase de forma idêntica em 2 tokens (aviso de isenção de responsabilidade P2).

 T_ECHO: echo T_ENCAPSED_AND_WHITESPACE/T_CONSTANT_ENCAPSED_STRING: "Some string" 

Além disso, para aspas simples sempre haverá T_CONSTANT_ENCAPSED_STRING, e para aspas duplas, quando for necessário. Se houver um espaço na linha, então T_ENCAPSED_AND_WHITESPACE.

Os códigos de operação serão simples de desonrar e absolutamente idênticos:

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


Conclusões


Se você deseja salvar alguns ciclos do processador no estágio de compilação, use seqüências de caracteres simples para seqüências de caracteres constantes.

Linha dinâmica


Existem 4 opções.

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

Para a primeira opção:

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

Para o segundo (para o terceiro também, somente em vez de períodos haverá vírgulas):

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

Pela quarta:

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

Mas com opcodes tudo será muito mais divertido.

Primeiro:

 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 

Segundo:

 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 

Terceiro:

 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' 

Quarto:

 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 

O senso comum nos diz que a opção com `printf` perderá velocidade nos três primeiros (especialmente porque no final ainda existe o mesmo ECHO), portanto deixaremos para tarefas em que a formatação é necessária e não nos lembraremos mais deste artigo.

Parece que a terceira opção é a mais rápida - imprimir três linhas consecutivas sem concatenações, ROPEs estranhas e a criação de variáveis ​​adicionais. Mas não é tão simples. A função de impressão no PHP certamente não é a Rocket Science, mas não é, de forma alguma, uma fonte banal de C- shny . Quem se importa - a bola se desenrola começando com php_output_write no arquivo main / output.c .

CONCAT. Tudo é simples aqui - convertemos, se necessário, os argumentos em strings e criamos um novo zend_string usando o memcpy rápido. O único aspecto negativo é que, com uma longa cadeia de concatenações para cada operação, novas linhas serão criadas deslocando os mesmos bytes de um lugar para outro.

Mas com ROPE_INIT, ROPE_ADD e ROPE_END tudo é muito mais interessante. Siga as mãos:

  1. ROPE_INIT (ext = 3, retorno = ~ 3, operandos = 'Hello +')
    Alocamos a “corda” de três slots (ext), colocamos a string 'Hello +' (operandos) no slot 0 e retornamos a variável temporária ~ 3 (return) que contém a “corda”.
  2. ROPE_ADD (ext = 1, retorno = ~ 3, operandos = ~ 3 ,! 0)
    Colocamos no espaço 1 (ext) da “corda” ~ 3 (operandos) a corda 'Vasya' obtida da variável! 0 (operandos) e retornamos a “corda” ~ 3 (retorno).
  3. ROPE_END (ext = 2, retorno = ~ 2, operandos = ~ 3, '% 21 + Tenha + um + bom + dia% 21')
    Colocamos a linha '% 21 + Have + a + nice + day% 21' (operandos) no slot 2 (ext), após o qual criamos um zend_string do tamanho necessário e copiamos todos os slots de “corda” nele, com o mesmo memcpy .

Separadamente, é importante notar que, no caso de constantes e variáveis ​​temporárias, os links para os dados serão colocados nos slots e não haverá cópia desnecessária.

Na minha opinião, bastante elegante. :)

Vamos avaliar. Como dados de origem, pegamos o arquivo zend_vm_execute.h (IMHO, isso será verdade) por 71 mil linhas e o imprimimos de 100 maneiras para 100 passes, diminuindo o mínimo e o máximo (cada medição começou 10 vezes, escolhendo a opção mais comum):

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

O que medimosTempo médio em segundos
"Corda"0,0129
Vários ECHO0,0135
Concatenação0,0158
printf , para completar0,0245

Conclusões


  1. Para cadeias de caracteres com substituição simples, de repente, aspas duplas são mais ideais que aspas simples com concatenação. E quanto mais longas as linhas forem usadas, maior será o ganho.
  2. Argumentos separados por vírgulas ... Existem muitas nuances. Por medição, é mais rápido que a concatenação e mais lento que o “cabo”, mas há muitas “variáveis” associadas à entrada / saída.

Conclusão


É difícil para mim encontrar uma situação em que possa surgir a necessidade de tais micro-otimizações. Ao escolher essa ou aquela abordagem, é mais razoável ser guiado por outros princípios - por exemplo, legibilidade do código ou estilo de codificação adotado pela sua empresa.

Quanto a mim, pessoalmente, não gosto da abordagem de concatenação por causa da aparência vyrviglazny, embora em alguns casos isso possa ser justificado.

PS Se esse tipo de análise é interessante - me avise - há muito mais que nem sempre é inequívoco e óbvio: uma variedade de objetos do VS, para cada VS enquanto VS para, sua opção ... :)

Uma pequena explicação da leitura dos comentários


A sintaxe HEREDOC e as "strings complexas" (onde as variáveis ​​estão entre chaves) são as mesmas strings de aspas duplas e compilam exatamente da mesma maneira.

Uma mistura de PHP com HTML como este:
 <?php $name = 'Vasya';?>Hello <?=$name?>! Have a nice day! 

Isso é apenas 3 ecos seguidos.

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


All Articles