PHP提供declare关键字和ticks关键字来声明ticks机制。如:declare(ticks = N); 这表示:在当前scope内,每执行N句internal statements(opcodes),就会中断当前的业务语句,去执行通过register_tick_function注册的函数(如果存在的话),然后再继续之前的代码。需要注意的是这里的N是指的PHP的一些OPCODE,而OPCODE与我们见到的PHP语句却不是一一对应的。
实例1:
$name = "phppan";
echo $name;
class Tipi {
public function test() {
echo "test";
}
}
function f_tipi() {
}
如上代码包括了我们常见的几种语句,赋值,输出,定义类,定义函数。通常我们用VLD查看PHP生成的中间代码,上面的代码通过php -dvld.active=1 t.php我们会看到 ECHO、ASSIGN、NOP等中间代码,如下所示:
Finding entry points
Branch analysis from position: 0
Jump found. (Code = 62) Position 1 = -2
filename: /home/revin/work/code/test/1.php
function name: (null)
number of ops: 7
compiled vars: !0 = $name
line #* E I O op fetch ext return operands
-------------------------------------------------------------------------------------
2 0 E > EXT_STMT
1 ASSIGN !0, 'phppan'
3 2 EXT_STMT
3 ECHO !0
4 4 EXT_STMT
9 5 EXT_STMT
10 6 > RETURN 1
branch: # 0; line: 2- 10; sop: 0; eop: 6; out1: -2
path #1: 0,
Function f_tipi:
Finding entry points
Branch analysis from position: 0
Jump found. (Code = 62) Position 1 = -2
filename: /home/revin/work/code/test/1.php
function name: f_tipi
number of ops: 3
compiled vars: none
line #* E I O op fetch ext return operands
-------------------------------------------------------------------------------------
9 0 E > EXT_NOP
10 1 EXT_STMT
2 > RETURN null
branch: # 0; line: 9- 10; sop: 0; eop: 2; out1: -2
path #1: 0,
End of function f_tipi
Class Tipi:
Function test:
Finding entry points
Branch analysis from position: 0
Jump found. (Code = 62) Position 1 = -2
filename: /home/revin/work/code/test/1.php
function name: test
number of ops: 5
compiled vars: none
line #* E I O op fetch ext return operands
-------------------------------------------------------------------------------------
5 0 E > EXT_NOP
6 1 EXT_STMT
2 ECHO 'test'
7 3 EXT_STMT
4 > RETURN null
branch: # 0; line: 5- 7; sop: 0; eop: 4; out1: -2
path #1: 0,
End of function test
End of class Tipi.
phppan
现在我们在示例1的代码上添加上ticks机制。如PHP代码示例2:
declare(ticks=1);
$name = "phppan";
echo $name;
class Tipi {
public function test() {
echo "test";
}
}
function f_tipi() {
}
示例2与示例1相比也就是多了第一条语句: declare(ticks=1); 如果我们此时再次通过VLD查看中间代码,会发现在每个中间代码的后面都多了一句中间代码:TICKS。
Finding entry points
Branch analysis from position: 0
Jump found. (Code = 62) Position 1 = -2
filename: /home/revin/work/code/test/1.php
function name: (null)
number of ops: 13
compiled vars: !0 = $name
line #* E I O op fetch ext return operands
-------------------------------------------------------------------------------------
2 0 E > EXT_STMT
1 TICKS
3 2 EXT_STMT
3 ASSIGN !0, 'phppan'
4 TICKS
4 5 EXT_STMT
6 ECHO !0
7 TICKS
5 8 EXT_STMT
9 TICKS
10 10 EXT_STMT
11 11 TICKS
12 > RETURN 1
branch: # 0; line: 2- 11; sop: 0; eop: 12; out1: -2
path #1: 0,
Function f_tipi:
Finding entry points
Branch analysis from position: 0
Jump found. (Code = 62) Position 1 = -2
filename: /home/revin/work/code/test/1.php
function name: f_tipi
number of ops: 3
compiled vars: none
line #* E I O op fetch ext return operands
-------------------------------------------------------------------------------------
10 0 E > EXT_NOP
11 1 EXT_STMT
2 > RETURN null
branch: # 0; line: 10- 11; sop: 0; eop: 2; out1: -2
path #1: 0,
End of function f_tipi
Class Tipi:
Function test:
Finding entry points
Branch analysis from position: 0
Jump found. (Code = 62) Position 1 = -2
filename: /home/revin/work/code/test/1.php
function name: test
number of ops: 6
compiled vars: none
line #* E I O op fetch ext return operands
-------------------------------------------------------------------------------------
6 0 E > EXT_NOP
7 1 EXT_STMT
2 ECHO 'test'
3 TICKS
8 4 EXT_STMT
5 > RETURN null
branch: # 0; line: 6- 8; sop: 0; eop: 5; out1: -2
path #1: 0,
End of function test
End of class Tipi.
phppan
是否因为ticks=1的原因而在每个中间代码的后面添加了TICKS?将declare(ticks=1);换成declare(ticks=100);,再次VLD,结果没有变化。从以上的结果可以看出,PHP内核在语法分析过程中实现了ticks机制。
声明ticks机制过程
声明的过程就是调用declare(ticks = N); 在语法分析时根据declare关键字和参数中的ticks关键字来声明ticks机制。通过zend_compile.c文件中的zend_do_declare_begin、declare_statement、zend_do_declare_end三个函数来编译声明ticks机制。在declare_statement函数中我们可以看到:declare除了可以声明ticks之外,还可以声明encoding,这在代码里面就是一个if else的判断。
ticks机制的声明仅在编译过程有用,它为后面的声明控制语句服务。其编译过程中的全局变量为:CG(declarables)。这是一个结构体,它仅有一个成员:ticks。当然后面应该还会有更多的成员出现。
声明控制语句
示例1和示例2已经充分说明在每条语句的语法分析时,会根据是否声明了ticks机制来添加TICKS中间代码,其实现在于每条语句在语法解析时都会添加一条函数调用:zend_do_ticks。从zend_language_parser.y文件中可以看出:zend_do_ticks函数添加在类定义语句,函数定义语句和常规语句的后面。 zend_compile.c文件中的zend_do_ticks函数会根据前面提到的 CG(declarables).ticks 来判断是否生成 ZEND_TICKS 中间代码(在VLD中看到的中间代码都是没有ZEND开头)。
除了声明ticks机制,还有执行。执行过程中关键的变量是在声明时的ticks=N。其实这里的N可以换个角度去理解:ticks指定的数字是指执行了多少次TICKS语句。在TICKS中间代码的执行函数ZEND_TICKS_SPEC_CONST_HANDLER中,会统计执行当前函数的次数,存储变量为EG(ticks_count)。当达到当初声明的界限,就会调用一次所有通过register_tick_function注册的函数,并计数清零。
与当初自己设想的实现相比,PHP内核对ticks机制的实现满足了功能单一原则和松耦合原则。将ticks机制作为一个中间代码添加到整个中间代码的执行体系中,包括状态的转移,函数的切换这些都是直接使用原有的机制。
ticks机制的应用场景
手册上说:Ticks 很适合用来做调试,以及实现简单的多任务,后台 I/O 和很多其它任务。
在调试过程中,对于定位一段特定代码中速度慢的语句比较有用,我们可以每执行两条低级语句就记录一次时间。虽然这个过程也可以用其它方法完成,但用 tick 更方便也更容易实现。
PCNTL也使用ticks机制来作为信号处理机制(signal handle callback mechanism),可以最小程度地降低处理异步事件时的负载。这里的关键在于PCNTL扩展的模块初始化函数(PHP_MINIT_FUNCTION(pcntl))。在此模块做模块初始化时,它会调用: php_add_tick_function(pcntl_signal_dispatch);将pcntl的分发执行函数添加到ticks机制的调用函数中去,从而当ticks触发时就会调用PCNTL扩展函数中指定的所有方法。
资料
PHP的ticks机制
php手册里,pcntl_signal函数解释,很多例子前面declare(ticks=1)这句话的意义
你知道PHP信号处理的正确打开方式吗?
PHP官方的pcntl_signal性能极差