设为首页 收藏本站
查看: 762|回复: 0

[经验分享] PHP源代码分析- tick(s)

[复制链接]

尚未签到

发表于 2017-4-2 09:59:12 | 显示全部楼层 |阅读模式
  
By Altair, http://www.phpinternals.com 转载请注明本信息

昨天有位朋友在杭州的PHPer群里面贴出了下面的一段代码并给出了运行结果:
源程序:
<?php
function doTicks ()
{
    echo 'Ticks';
}
register_tick_function('doTicks');
declare(ticks = 1) {
    for ($x = 1; $x < 10; ++ $x) {
        echo $x * $x . '<br />';
    }
}
?>运行结果:

  • 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:
[php] 
    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]很明显,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的三条语法规则:

  • statement:
  •   unticked_statement { zend_do_ticks(TSRMLS_C); }
  • | ...
  • ;
  • function_declaration_statement:
  •   unticked_function_declaration_statement { zend_do_ticks(TSRMLS_C); }
  • ;
  • class_declaration_statement:
  •   unticked_class_declaration_statement { zend_do_ticks(TSRMLS_C); }
  • ;

复制代码
也就是说,PHP编译会在statement(语句), function_declaration_statement(函数定义语句), class_declaration_statement后插入TICKS处理函数,即它会在每条statement,函数声明,类(实际上还包括接口)声明后插入一条TICKS指令。函数与类声明语句比较好理解,根据unticked_function_declaration_statement的语法定义:

  • unticked_function_declaration_statement:
  •   function is_reference T_STRING { zend_do_begin_function_declaration(&$1, &$3, 0, $2.op_type, NULL TSRMLS_CC); }
  •    '(' parameter_list ')' '{' inner_statement_list '}' { zend_do_end_function_declaration(&$1 TSRMLS_CC); }
  • ;

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

  • unticked_class_declaration_statement:
  •   class_entry_type T_STRING extends_from
  •    { zend_do_begin_class_declaration(&$1, &$2, &$3 TSRMLS_CC); }
  •    implements_list
  •    '{'
  •     class_statement_list
  •    '}' { zend_do_end_class_declaration(&$1, &$2 TSRMLS_CC); }
  • | interface_entry T_STRING
  •    { zend_do_begin_class_declaration(&$1, &$2, NULL TSRMLS_CC); }
  •    interface_extends_list
  •    '{'
  •     class_statement_list
  •    '}' { zend_do_end_class_declaration(&$1, &$2 TSRMLS_CC); }
  • ;

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

  • unticked_statement:
  •   '{' inner_statement_list '}'
  • | 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); }
  • | 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); }
  • | 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); }
  • | 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); }
  • | T_FOR
  •    '('
  •     for_expr
  •    ';' { zend_do_free(&$3 TSRMLS_CC); $4.u.opline_num = get_next_op_number(CG(active_op_array)); }
  •     for_expr
  •    ';' { zend_do_extended_info(TSRMLS_C); zend_do_for_cond(&$6, &$7 TSRMLS_CC); }
  •     for_expr
  •    ')' { zend_do_free(&$9 TSRMLS_CC); zend_do_for_before_statement(&$4, &$7 TSRMLS_CC); }
  •    for_statement { zend_do_for_end(&$7 TSRMLS_CC); }
  • | T_SWITCH '(' expr ')' { zend_do_switch_cond(&$3 TSRMLS_CC); } switch_case_list { zend_do_switch_end(&$6 TSRMLS_CC); }
  • | T_BREAK ';'    { zend_do_brk_cont(ZEND_BRK, NULL TSRMLS_CC); }
  • | T_BREAK expr ';'  { zend_do_brk_cont(ZEND_BRK, &$2 TSRMLS_CC); }
  • | T_CONTINUE ';'   { zend_do_brk_cont(ZEND_CONT, NULL TSRMLS_CC); }
  • | T_CONTINUE expr ';'  { zend_do_brk_cont(ZEND_CONT, &$2 TSRMLS_CC); }
  • | T_RETURN ';'      { zend_do_return(NULL, 0 TSRMLS_CC); }
  • | T_RETURN expr_without_variable ';' { zend_do_return(&$2, 0 TSRMLS_CC); }
  • | T_RETURN variable ';'    { zend_do_return(&$2, 1 TSRMLS_CC); }
  • | T_GLOBAL global_var_list ';'
  • | T_STATIC static_var_list ';'
  • | T_ECHO echo_expr_list ';'
  • | T_INLINE_HTML   { zend_do_echo(&$1 TSRMLS_CC); }
  • | expr ';'    { zend_do_free(&$1 TSRMLS_CC); }
  • | T_UNSET '(' unset_variables ')' ';'
  • | T_FOREACH '(' variable T_AS
  •   { zend_do_foreach_begin(&$1, &$2, &$3, &$4, 1 TSRMLS_CC); }
  •   foreach_variable foreach_optional_arg ')' { zend_do_foreach_cont(&$1, &$2, &$4, &$6, &$7 TSRMLS_CC); }
  •   foreach_statement { zend_do_foreach_end(&$1, &$4 TSRMLS_CC); }
  • | T_FOREACH '(' expr_without_variable T_AS
  •   { zend_do_foreach_begin(&$1, &$2, &$3, &$4, 0 TSRMLS_CC); }
  •   variable foreach_optional_arg ')' { zend_check_writable_variable(&$6); zend_do_foreach_cont(&$1, &$2, &$4, &$6, &$7 TSRMLS_CC); }
  •   foreach_statement { zend_do_foreach_end(&$1, &$4 TSRMLS_CC); }
  • | 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); }
  • | ';'  /* empty statement */
  • | T_TRY { zend_do_try(&$1 TSRMLS_CC); } '{' inner_statement_list '}'
  •   T_CATCH '(' { zend_initialize_try_catch_element(&$1 TSRMLS_CC); }
  •   fully_qualified_class_name { zend_do_first_catch(&$7 TSRMLS_CC); }
  •   T_VARIABLE ')' { zend_do_begin_catch(&$1, &$9, &$11, &$7 TSRMLS_CC); }
  •   '{' inner_statement_list '}' { zend_do_end_catch(&$1 TSRMLS_CC); }
  •   additional_catches { zend_do_mark_last_catch(&$7, &$18 TSRMLS_CC); }
  • | T_THROW expr ';' { zend_do_throw(&$2 TSRMLS_CC); }
  • | T_GOTO T_STRING ';' { zend_do_goto(&$2 TSRMLS_CC); }
  • ;

复制代码
  根据上面的定义,我们知道,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

运维网声明 1、欢迎大家加入本站运维交流群:群②:261659950 群⑤:202807635 群⑦870801961 群⑧679858003
2、本站所有主题由该帖子作者发表,该帖子作者与运维网享有帖子相关版权
3、所有作品的著作权均归原作者享有,请您和我们一样尊重他人的著作权等合法权益。如果您对作品感到满意,请购买正版
4、禁止制作、复制、发布和传播具有反动、淫秽、色情、暴力、凶杀等内容的信息,一经发现立即删除。若您因此触犯法律,一切后果自负,我们对此不承担任何责任
5、所有资源均系网友上传或者通过网络收集,我们仅提供一个展示、介绍、观摩学习的平台,我们不对其内容的准确性、可靠性、正当性、安全性、合法性等负责,亦不承担任何法律责任
6、所有作品仅供您个人学习、研究或欣赏,不得用于商业或者其他用途,否则,一切后果均由您自己承担,我们对此不承担任何法律责任
7、如涉及侵犯版权等问题,请您及时通知我们,我们将立即采取措施予以解决
8、联系人Email:admin@iyunv.com 网址:www.yunweiku.com

所有资源均系网友上传或者通过网络收集,我们仅提供一个展示、介绍、观摩学习的平台,我们不对其承担任何法律责任,如涉及侵犯版权等问题,请您及时通知我们,我们将立即处理,联系人Email:kefu@iyunv.com,QQ:1061981298 本贴地址:https://www.yunweiku.com/thread-358983-1-1.html 上篇帖子: 使用phpize建立php扩展 Cannot find config.m4. 下篇帖子: php 计算UPS运费 ( UPS shipping cost )
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

扫码加入运维网微信交流群X

扫码加入运维网微信交流群

扫描二维码加入运维网微信交流群,最新一手资源尽在官方微信交流群!快快加入我们吧...

扫描微信二维码查看详情

客服E-mail:kefu@iyunv.com 客服QQ:1061981298


QQ群⑦:运维网交流群⑦ QQ群⑧:运维网交流群⑧ k8s群:运维网kubernetes交流群


提醒:禁止发布任何违反国家法律、法规的言论与图片等内容;本站内容均来自个人观点与网络等信息,非本站认同之观点.


本站大部分资源是网友从网上搜集分享而来,其版权均归原作者及其网站所有,我们尊重他人的合法权益,如有内容侵犯您的合法权益,请及时与我们联系进行核实删除!



合作伙伴: 青云cloud

快速回复 返回顶部 返回列表