(转载)PHP源代码分析- tick(s)
(转载)http://bbs.phpchina.com/forum.php?mod=viewthread&tid=94534
昨天有位朋友在杭州的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的应用。
(转载)PHP源代码分析- tick(s)的更多相关文章
- 【转载】linux环境下tcpdump源代码分析
linux环境下tcpdump源代码分析 原文时间 2013-10-11 13:13:02 CSDN博客 原文链接 http://blog.csdn.net/han_dawei/article/d ...
- [转载] FFmpeg源代码简单分析:常见结构体的初始化和销毁(AVFormatContext,AVFrame等)
===================================================== FFmpeg的库函数源代码分析文章列表: [架构图] FFmpeg源代码结构图 - 解码 F ...
- Cocos2d-x 源代码分析 : Scheduler(定时器) 源代码分析
源代码版本号 3.1r,转载请注明 我也最终不out了,開始看3.x的源代码了.此时此刻的心情仅仅能是wtf! !!!!!!! !.只是也最终告别CC时代了. cocos2d-x 源代码分析文件夹 h ...
- RingBuffer源代码分析
看到一篇写的非常详细的帖子,为防止楼主删帖后找不到,果断转载过来 RingBuffer源代码分析 出处:http://bbs.ickey.cn/community/forum.php?mod=view ...
- 服务器程序源代码分析之三:gunicorn
服务器程序源代码分析之三:gunicorn 时间:2014-05-09 11:33:54 类别:网站架构 访问: 641 次 gunicorn是一个python web 服务部署工具,类似flup,完 ...
- Raid1源代码分析--开篇总述
前段时间由于一些事情耽搁了,最近将raid1方面的各流程整理了一遍.网上和书上,能找到关于MD下的raid1的文档资料比较少.决定开始写一个系列的关于raid1的博客,之前写过的一篇读流程也会在之后加 ...
- Android系统进程Zygote启动过程的源代码分析
文章转载至CSDN社区罗升阳的安卓之旅,原文地址:http://blog.csdn.net/luoshengyang/article/details/6768304 在Android系统中,所有的应用 ...
- Android应用程序安装过程源代码分析
文章转载至CSDN社区罗升阳的安卓之旅,原文地址:http://blog.csdn.net/luoshengyang/article/details/6766010 Android系统在启动的过程中, ...
- Android应用程序进程启动过程的源代码分析
文章转载至CSDN社区罗升阳的安卓之旅,原文地址: http://blog.csdn.net/luoshengyang/article/details/6747696 Android 应用程序框架层创 ...
随机推荐
- session marked for kill处理oracle中杀不掉的锁
ora-00031:session marked for kill处理oracle中杀不掉的锁 一些ORACLE中的进程被杀掉后,状态被置为"killed",但是锁定的资源很长 ...
- C# 泛型2
我们在编写程序时,经常遇到两个模块的功能非常相似,只是一个是处理int数据,另一个是处理string数据,或者其他自定义的数据类型,但我们没有办法,只能分别写多个方法处理每个数据类型,因为方法的参数类 ...
- Ubuntu 12.04下解决Rhythmbox Music Player乱码问题
1.打开终端输入如下信息: $ sudo gedit ~/.profile 2.在打开的文档末尾加上如下两句: export GST_ID3_TAG_ENCODING=GBK:UTF-8:GB1803 ...
- 2016年9月ccf
去长沙理工考ccf.恰好又可以见闺蜜. 前2道题很简单,第三题题目太长就跳过了[绕来绕去就像“你儿子是我儿子的爸爸一样头疼”],就做第四题.但是还有最后一个部分没写写好就到点了. 现在把它补充完整. ...
- 『重构--改善既有代码的设计』读书笔记----Introduce Local Extension
同Introduce Foreign Method一样,很多时候你不能修改编辑原始类,你需要为这些服务类增加一些额外的函数,但你没有这个权限或者入口.如果你只需要一个或者两个外加函数那么你可以放心的使 ...
- lmsw - 加载机器状态字
将源操作数加载到机器状态字,即寄存器 CR0 的位 0 到 15.源操作数可以是 16 位通用寄存器或内存位置.只有源操作数的低 4 位(也就是 PE.MP.EM 及 TS 标志)会加载到 CR0.C ...
- jQuery 源码基本框架
抽丝剥茧, 7000+ 行的 jQuery 源码基本可以概括为以下的伪代码 (function (window, undefined) { //将 document 封装成 jQuery 对象并缓存 ...
- Bootstrap_表单_表单控件状态
一.焦点状态 焦点状态是通过伪类“:focus”来实现.Bootstrap框架中表单控件的焦点状态删除了outline的默认样式,重新添加阴影效果. <form role="form& ...
- dedecms 文章内容文章名字和文章网址的调用
文章标题: <a href="{dede:field name='arcurl'/}">{dede:field.title/}</a> 本文章网址: < ...
- JQUERY1.9学习笔记 之属性选择器(一) 前缀选择器
描述:选择指定属性值的元素,或者是以字符串开始其后跟随“-”符号的. jQuery( "[attribute|='value']" ) 例:查找出所有语言属性为en的链接. < ...