的PHP 迷人的引号


至于PHP的微优化,通过将双引号替换为单引号,会破坏很多副本,因此重新编写流是很成问题的。 但是我会尽力的。

在本文中,将只有一个基准,如果没有基准,它将是什么,并且主要重点是分析它在内部的排列方式。

免责声明


  1. 下文所述的大部分程序大部分时间都在纳秒级以下,实际上,它什么也做不了,只不过浪费了这种微优化所浪费的时间。 对于编译时间的“优化”尤其如此。
  2. 我将削减代码并最大程度地输出,仅保留本质。
  3. 撰写文章时,我使用了PHP 7.2

必要的入门


在编译阶段 ,双引号的字符串与单引号的字符串的处理方式略有不同。

单引号将被解析为:

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

因此:

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

在有关PHP微优化的文章中,经常建议不要使用print ,因为它比echo慢。 让我们看看它们如何排序。

解析echo

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

解析打印

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

即 通常,是的, 回声是在较早的步骤中检测到的,应该注意,这一步骤非常困难。

为了避免在本文中再次引起注意,我们将牢记在编译阶段,双引号会丢失单引号,而打印会丢失echo 。 另外,请不要忘记,在最坏的情况下,大约是十亿分之一秒。

好吧,以免起床两次。 这是用于编译printecho的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"; 将几乎完全分为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! 

对于第二个(对于第三个也是如此,只有逗号而不是句点):

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

对于第四:

 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' 

第四名:

 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的选项将在前三个选项中失去速度(特别是由于最后仍然存在相同的ECHO),因此我们将其留给需要格式化的任务,我们将在本文中记不清了。

第三种选择似乎是最快的-连续打印三行,而无需串联,奇怪的ROPE和创建其他变量。 但不是那么简单。 PHP中的print函数当然不是Rocket Science,但绝不是普通的C-shny fputs 。 谁在乎 -球从main / output.c文件中的php_output_write开始解散。

CONCAT。 这里的一切都很简单-如果需要,我们将参数转换为字符串,并使用快速memcpy创建新的zend_string 。 唯一的负面影响是,对于每个操作,如果有很长的串联链接,则将不同的字节从一个地方移到另一个地方将创建新行。

但是有了ROPE_INIT,ROPE_ADD和ROPE_END,一切都变得更加有趣了。 跟随手:

  1. ROPE_INIT(ext = 3,return =〜3,操作数='Hello +')
    我们从三个插槽(扩展名)分配“绳索”,将字符串“ Hello +”(操作数)放入插槽0,然后返回包含“绳索”的临时变量〜3(返回)。
  2. ROPE_ADD(ext = 1,return =〜3,操作数=〜3,!0)
    我们将“ rope”〜3(操作数)的插槽1(ext)放入从变量!0(操作数)获得的字符串“ Vasya”,并返回“ rope”〜3(返回)。
  3. ROPE_END(ext = 2,return =〜2,操作数=〜3,'%21 + Have + a + nice + day%21')
    我们将'%21 + Have + a + nice + day%21'行(操作数)放入插槽2(扩展名)中,然后创建所需大小的zend_string,并使用相同的memcpy依次将所有“ rope”插槽复制到该行中。

另外,值得注意的是,在常量和临时变量的情况下,指向数据的链接将放置在插槽中,并且不会有不必要的复制。

在我看来,非常优雅。 :)

让我们进行基准测试。 作为源数据,我们将文件zend_vm_execute.h (恕我直言,这将是真的)用于71000行,并以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

结论


  1. 对于具有简单替换的字符串,突然出现的双引号要比带有串联的单引号更好。 并且使用的线越长,增益越大。
  2. 以逗号分隔的参数。有许多细微差别。 通过测量,它比连接快,但比“绳索”慢,但是与输入/输出关联的“变量”太多。

结论


对于我而言,很难提出可能需要进行此类微优化的情况。 选择这种方式时,以其他原则为指导更为合理-例如,代码可读性或贵公司采用的编码风格。

就我个人而言,由于vyrviglazny的外观,我不喜欢使用串联方法,尽管在某些情况下它是合理的。

PS:如果这种分析很有趣-让我知道-还有很多事情远非总是一目了然:VS对象数组,foreach VS,而VS for,您可以选择... :)

阅读评论的一些解释


HEREDOC语法和“复杂字符串”(变量在大括号内)是相同的双引号字符串,并且以完全相同的方式进行编译。

PHP与HTML的混合如下:
 <?php $name = 'Vasya';?>Hello <?=$name?>! Have a nice day! 

这只是连续3个回声

Source: https://habr.com/ru/post/zh-CN447416/


All Articles