1. 程式人生 > >關於php的declare語句中的tick的解釋 [轉]

關於php的declare語句中的tick的解釋 [轉]

<?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的應用