ZEND_TICKS と tick 関数

この記事は 闇PHP Advent Calendar 2015 4日目 です

tick について

ZEND_TICKS とは、tick 不可能行*1の後に発行されるオペコードで、多くは、セミコロンで終わるステートメントごとに発行されます。

ただし、デフォルトでは発行されず、利用するためには declare 命令で明示的に有効にする必要があります。
declare 命令の ticks で指定した回数の ZEND_TICKS が呼ばれるごとにregister_tick_function で登録した関数 (tick 関数) が実行されます。


code.1 と code.2 は(内部的には異なりますが)表面上同じ動きをします。

code.1
<?php
function tick() {
  echo "tick!" . PHP_EOL;
}

register_tick_function('tick');
declare(ticks=1);

$a = 1;
if ($a < 10) {
    echo $a . PHP_EOL;
}
code.2
<?php
function tick() {
  echo "tick!" . PHP_EOL;
}

tick();
$a = 1;
tick();
if ($a < 10) {
    tick();
    echo "{$a}" . PHP_EOL;
    tick();
    tick();
}
tick();

echo の後に 2回 tick が走りますが、これは誤りではなく、echo による tick と if の条件内ステートメントリスト(表面上は見えない) による tick です。

declare(tick=N) の有効範囲

declare による ticks の有効範囲は、少々特殊なので注意が必要です。

declare(ticks=1);
declare(ticks=1) {}

のように、単文あるいは空ブロックで指定された場合、ファイル内の declare 以降全てが範囲となり、

declare(ticks=1) sleep(1);

declare(ticks=1) {
	sleep(1);
	echo "hoge";
}

のように、他の文を伴った場合は、その文やブロックのみが範囲となります。
あまり使うことはないと思いますが、ブロックは入れ子にすることも出来ます。

また、ticks は別ファイルには影響しない事にも注意が必要です。
これは、 ZEND_TICKS の発行がコンパイル時に行われるためです。

ZEND_TICKS オペコードが発行されている部分(ticks > 0)のスクリプト実行は、発行されていない部分と比較して (オペコードや tick 関数を処理する分) オーバヘッドがあることに注意しましょう。

code.3
<?php
// ticks=0 (初期値 ZEND_TICKS 発行なし)

declare(ticks=1);
// tick=1

declare(ticks=2) {
	[any code]
	// ticks=2
}
// ticks=1

require 'hoge.php'; // hoge.php には ticks は引き継がれない (ticks=0 で開始される)

echo "any code"; // もちろんここでも tick=1 のまま

*1:php5.6 までは zend_language_parser.y における unticked_statement として定義され、パース時に発行していましたがが、 php7 においては AST 導入によりコンパイル時発行となったためその定義はなくなり、代わりに zend_is_unticked_stmt 関数で表されています。