• PHP源代码分析 tick(s)


    昨天有位朋友在杭州的PHPer群里面贴出了下面的一段代码并给出了运行结果:
    程序

     1 <?php
    2 function doTicks ()
    3 {
    4 echo 'Ticks';
    5 }
    6 register_tick_function('doTicks');
    7 declare(ticks = 1) {
    8 for ($x = 1; $x < 10; ++ $x) {
    9 echo $x * $x . '<br />';
    10 }
    11 }
    12 ?>

    运行结果:

    1

    TicksTicks4

    TicksTicks9

    TicksTicks16

    TicksTicks25

    TicksTicks36

    TicksTicks49

    TicksTicks64

    TicksTicks81

    TicksTicksTicksTicks

    他对运行结果感到疑惑,问了三个问题:
    (1) 为什么先输出1之后才输出“Ticks”? 
    (2) 为什么在输出81后还输出四个Ticks ? 
    (3)  declare中的for循环怎么分解成低级语句(low-level)?

    这是每个初次接触ticks的人都会碰到的问题。首先register_tick_function函数定义了每个tick事件发生时的处理函数。那么什么是tick事件呢?先看手册上对ticks的解释:
    A tick is an event that occurs for every N low-level statements executed by the parser within the declare block. The value for N is specified using ticks=N within the declare blocks's directive section. 
    The event(s) that occur on each tick are specified using the register_tick_function().
    这个解释有三层意思:(1) tick是一个事件。(2) tick事件在PHP每执行N条低级语句就发生一次,N由declare语句指定。(3)可以用register_tick_function()来指定tick事件发生时应该执行的操作。
    很明显,理解上面的输出结果最关键的是了解什么是低级语句(low-level statements),它又是如何进行计数的。我们首先还是将上面的程序通过OPDUMP编译成OPCODEs:

        1: <?php
    0 NOP
    2:
    3: function doTicks ()
    4: {
    5: echo 'Ticks';
    0 ECHO 'Ticks'
    6: }
    1 RETURN null
    7: register_tick_function('doTicks');
    1 SEND_VAL 'doTicks'
    2 DO_FCALL 'register_tick_function' [extval:1]
    8: declare(ticks = 1) {
    9: for ($x = 1; $x < 10; ++ $x) {
    3 ASSIGN !0, 1
    4 IS_SMALLER !0, 10 =>RES[~2]
    5 JMPZNZ ~2, ->14 [extval:8]
    6 PRE_INC !0
    7 JMP ->4
    10: echo $x * $x . '<br />';
    8 MUL !0, !0 =>RES[~4]
    9 CONCAT ~4, '<br />' =>RES[~5]
    10 ECHO ~5
    11 TICKS 1 =>RES[]
    11: }
    12 TICKS 1 =>RES[]
    13 JMP ->6
    14 TICKS 1 =>RES[]
    12: }
    15 TICKS 1 =>RES[]
    16 RETURN 1

    很明显,PHP的编译过程已经在编译后每条语句的OPCODE序列中插入了TICKS指令用于处理tick事件。那么这些TICKS是根据什么规则来插入的呢?
    我们还是从PHP Zend Engine的源代码中寻找答案。通过简单的文本搜索我们可以知道生成ZEND_TICKS指令的唯一函数是zend_do_ticks(该函数的实现在zend_compile.c中)。现在再从PHP的语法分析文件zend_language_parser.y(PHP使用bison来做语法分析,所有的语法规则均定义在zend_language_parser.y中)中寻找调用zend_do_ticks的地方。再一次使用简单的文本搜索,我们可以得到调用zend_do_ticks的三条语法规则:

     1 statement:
    2
    3 unticked_statement { zend_do_ticks(TSRMLS_C); }
    4
    5 | ...
    6
    7 ;
    8
    9 function_declaration_statement:
    10
    11 unticked_function_declaration_statement { zend_do_ticks(TSRMLS_C); }
    12
    13 ;
    14
    15 class_declaration_statement:
    16
    17 unticked_class_declaration_statement { zend_do_ticks(TSRMLS_C); }
    18
    19 ;

    可以知道function_declaration_statement是完整的函数声明,也就是说,一个函数的定义包括完整的函数原形及函数体算一条function_declaration_statement。
    同样从unticked_class_declaration_statement的语法定义:

     1 unticked_class_declaration_statement:
    2
    3 class_entry_type T_STRING extends_from
    4
    5 { zend_do_begin_class_declaration(&$1, &$2, &$3 TSRMLS_CC); }
    6
    7 implements_list
    8
    9 '{'
    10
    11 class_statement_list
    12
    13 '}' { zend_do_end_class_declaration(&$1, &$2 TSRMLS_CC); }
    14
    15 | interface_entry T_STRING
    16
    17 { zend_do_begin_class_declaration(&$1, &$2, NULL TSRMLS_CC); }
    18
    19 interface_extends_list
    20
    21 '{'
    22
    23 class_statement_list
    24
    25 '}' { zend_do_end_class_declaration(&$1, &$2 TSRMLS_CC); }
    26
    27 ;

    也可以知道,完整的class或interface定义算是一个class_declration_statement。
    最复杂的是statement,它的核心是下面的定义:

     1 unticked_statement:
    2
    3 '{' inner_statement_list '}'
    4
    5 | T_IF '(' expr ')' { zend_do_if_cond(&$3, &$4 TSRMLS_CC); } statement { zend_do_if_after_statement(&$4, 1 TSRMLS_CC); } elseif_list else_single { zend_do_if_end(TSRMLS_C); }
    6
    7 | T_IF '(' expr ')' ':' { zend_do_if_cond(&$3, &$4 TSRMLS_CC); } inner_statement_list { zend_do_if_after_statement(&$4, 1 TSRMLS_CC); } new_elseif_list new_else_single T_ENDIF ';' { zend_do_if_end(TSRMLS_C); }
    8
    9 | T_WHILE '(' { $1.u.opline_num = get_next_op_number(CG(active_op_array)); } expr ')' { zend_do_while_cond(&$4, &$5 TSRMLS_CC); } while_statement { zend_do_while_end(&$1, &$5 TSRMLS_CC); }
    10
    11 | T_DO { $1.u.opline_num = get_next_op_number(CG(active_op_array)); zend_do_do_while_begin(TSRMLS_C); } statement T_WHILE '(' { $5.u.opline_num = get_next_op_number(CG(active_op_array)); } expr ')' ';' { zend_do_do_while_end(&$1, &$5, &$7 TSRMLS_CC); }
    12
    13 | T_FOR
    14
    15 '('
    16
    17 for_expr
    18
    19 ';' { zend_do_free(&$3 TSRMLS_CC); $4.u.opline_num = get_next_op_number(CG(active_op_array)); }
    20
    21 for_expr
    22
    23 ';' { zend_do_extended_info(TSRMLS_C); zend_do_for_cond(&$6, &$7 TSRMLS_CC); }
    24
    25 for_expr
    26
    27 ')' { zend_do_free(&$9 TSRMLS_CC); zend_do_for_before_statement(&$4, &$7 TSRMLS_CC); }
    28
    29 for_statement { zend_do_for_end(&$7 TSRMLS_CC); }
    30
    31 | T_SWITCH '(' expr ')' { zend_do_switch_cond(&$3 TSRMLS_CC); } switch_case_list { zend_do_switch_end(&$6 TSRMLS_CC); }
    32
    33 | T_BREAK ';' { zend_do_brk_cont(ZEND_BRK, NULL TSRMLS_CC); }
    34
    35 | T_BREAK expr ';' { zend_do_brk_cont(ZEND_BRK, &$2 TSRMLS_CC); }
    36
    37 | T_CONTINUE ';' { zend_do_brk_cont(ZEND_CONT, NULL TSRMLS_CC); }
    38
    39 | T_CONTINUE expr ';' { zend_do_brk_cont(ZEND_CONT, &$2 TSRMLS_CC); }
    40
    41 | T_RETURN ';' { zend_do_return(NULL, 0 TSRMLS_CC); }
    42
    43 | T_RETURN expr_without_variable ';' { zend_do_return(&$2, 0 TSRMLS_CC); }
    44
    45 | T_RETURN variable ';' { zend_do_return(&$2, 1 TSRMLS_CC); }
    46
    47 | T_GLOBAL global_var_list ';'
    48
    49 | T_STATIC static_var_list ';'
    50
    51 | T_ECHO echo_expr_list ';'
    52
    53 | T_INLINE_HTML { zend_do_echo(&$1 TSRMLS_CC); }
    54
    55 | expr ';' { zend_do_free(&$1 TSRMLS_CC); }
    56
    57 | T_UNSET '(' unset_variables ')' ';'
    58
    59 | T_FOREACH '(' variable T_AS
    60
    61 { zend_do_foreach_begin(&$1, &$2, &$3, &$4, 1 TSRMLS_CC); }
    62
    63 foreach_variable foreach_optional_arg ')' { zend_do_foreach_cont(&$1, &$2, &$4, &$6, &$7 TSRMLS_CC); }
    64
    65 foreach_statement { zend_do_foreach_end(&$1, &$4 TSRMLS_CC); }
    66
    67 | T_FOREACH '(' expr_without_variable T_AS
    68
    69 { zend_do_foreach_begin(&$1, &$2, &$3, &$4, 0 TSRMLS_CC); }
    70
    71 variable foreach_optional_arg ')' { zend_check_writable_variable(&$6); zend_do_foreach_cont(&$1, &$2, &$4, &$6, &$7 TSRMLS_CC); }
    72
    73 foreach_statement { zend_do_foreach_end(&$1, &$4 TSRMLS_CC); }
    74
    75 | T_DECLARE { $1.u.opline_num = get_next_op_number(CG(active_op_array)); zend_do_declare_begin(TSRMLS_C); } '(' declare_list ')' declare_statement { zend_do_declare_end(&$1 TSRMLS_CC); }
    76
    77 | ';' /* empty statement */
    78
    79 | T_TRY { zend_do_try(&$1 TSRMLS_CC); } '{' inner_statement_list '}'
    80
    81 T_CATCH '(' { zend_initialize_try_catch_element(&$1 TSRMLS_CC); }
    82
    83 fully_qualified_class_name { zend_do_first_catch(&$7 TSRMLS_CC); }
    84
    85 T_VARIABLE ')' { zend_do_begin_catch(&$1, &$9, &$11, &$7 TSRMLS_CC); }
    86
    87 '{' inner_statement_list '}' { zend_do_end_catch(&$1 TSRMLS_CC); }
    88
    89 additional_catches { zend_do_mark_last_catch(&$7, &$18 TSRMLS_CC); }
    90
    91 | T_THROW expr ';' { zend_do_throw(&$2 TSRMLS_CC); }
    92
    93 | T_GOTO T_STRING ';' { zend_do_goto(&$2 TSRMLS_CC); }
    94
    95 ;

    根据上面的定义,我们知道,statement包括:
    根据上面的定义,我们知道,statement包括:
    (1) 简单语句:空语句(就一个;号),return, break, continue, throw, goto, global, static, unset, echo,  内置的HTML文本,分号结束的表达式等均算一个语句。
    (2) 复合语句:完整的if/elseif, while, do...while, for, foreach, switch, try...catch等算一个语句。
    (3) 语句块:{} 括出来的语句块。
    (4) 最后特别的:declare块本身也算一个语句(按道理declare块也算是复合语句,但此处特意将其独立出来)。

    所有的statement, function_declare_statement, class_declare_statement就构成了所谓的低级语句(low-level statement)。

    现在再来看开始的例子就比较好理解了:

    首先完整的for循环算一个语句,但必须要等循环结束才算,因此在编译时for循环里面的echo 算第一个语句。所以第一个doTicks是在第一个echo后执行的,也就是1输出后才发生第一个tick事件。在$x 从1到9的循环中,每个循环包括两个语句,一个echo, 一个for循环。在81输出后,因为echo是一条语句,因此输出第一个ticks. 同时$x=9的这个for循环也结束了,这又是一条语句,输出第二个ticks;开始$x=10的循环,但这时已不满足循环条件,for循环执行结束,这个循环又是一个语句,这时输出第三个ticks。最后declare本身也算一条语句,所以又输出第四个ticks。

    说了半天,ticks到底有什么用?实际上可用tick来进行调试,性能测试,实现简单的多任务,或者做一些后台的I/O操作等等。大家可以在网页http://www.php.net/manual/en/control-structures.declare.php 
    后面找到一些有趣的tick的应用。

    转自 http://bbs.phpchina.com/viewthread.php?tid=94534

  • 相关阅读:
    【java】-- java并发包总结
    【Linux】-- Linux上java运行环境的配置(JDK+TOMCAT)
    【Redis】-- 安装及配置
    【redis】-- springboot集成redis及使用
    【写法规范】-- 设计请求返回接口与封装
    每日CSS_发光文本效果
    每日CSS_实时时钟效果
    每日CSS_霓虹灯按钮悬停效果
    CSS3全览_动画+滤镜
    CSS3全览_最新布局
  • 原文地址:https://www.cnblogs.com/sunbin/p/2116848.html
Copyright © 2020-2023  润新知