最近、気になる情報を目にしたらしい。
よく読んでみると、「ReflectionClass::getDocComment()」に関係する仕様らしい。
怖いので、3パターンほど試してみた。(コードがちょっと分かりづらくて申し訳ないものだが)実際に動かしたコードは後述する。
今回、実験に使用するコードでは、以下のことを行なう。
- 3パターンのPHPコードを動的に生成し、ファイルに保存する。
- それらのファイルを、順にinclude_onceする。
- 実際に試してもらうと分かるが、include/include_once/require/require_onceのどれでも結果は変わらない。
- 増加したメモリ使用量を出力する。
- memory_get_usage()と、念のためmemory_get_usage(true)の値の両方を出力。
3パターンのPHPコードとは以下のようなもの。
<?php
class Hoge1 {}
- パターン2:「/** 〜 */」形式のコメントの後に「/* */」を挟み込んでみる
<?php
class Hoge2 {}
- パターン3:「/** 〜 */」形式のコメントの後に「/** */」を挟み込んでみる
<?php
class Hoge3 {}
以下が、実際に動かしたコード。
各パターンでの、各クラスのドキュメントコメントも取得して、長さを出力してみている。
<?php
function _printMemory($msg) {
static $pre1 = -1;
static $pre2 = -1;
$mem1 = memory_get_usage();
$mem2 = memory_get_usage(true);
$diff1 = ($pre1 < 0 ? 0 : $mem1 - $pre1);
$diff2 = ($pre2 < 0 ? 0 : $mem2 - $pre2);
printf("%-16s: ", $msg);
printf("%16d | %16d (%16d | %16d)\n", $mem1, $mem2, $diff1, $diff2);
$pre1 = $mem1;
$pre2 = $mem2;
}
_printMemory('init');
$dir = dirname($_SERVER['SCRIPT_FILENAME']);
if ($_GET) {
if (isset($_GET['cnt'])) {
$cnt = (int) $_GET['cnt'];
} else {
echo "Please specify GET parameter named 'cnt'";
exit(1);
}
} else {
if ($argc != 2) {
printf("Usage: %s <cnt>\n", $argv[0]);
printf("\n");
printf(" <cnt> Count of tags in comment.\n");
exit(1);
}
$cnt = (int) $argv[1];
}
$cmtBody = '';
for ($i = 0; $i < $cnt; ++$i) {
$cmtBody .= sprintf("@param hoge%05d %05d\n", $i, $i);
}
$files = array(
"{$dir}/foo-1.php",
"{$dir}/foo-2.php",
"{$dir}/foo-3.php",
);
$sources = array(
array(
'<?php ' . "\n" . '/* ' . "\n",
'<?php ' . "\n" . '/**' . "\n",
'<?php ' . "\n" . '/**' . "\n",
),
$cmtBody,
"*/\n",
array(
"/* */\n",
"/* */\n",
"/** */\n",
),
array(
'class Hoge1 {}' . "\n",
'class Hoge2 {}' . "\n",
'class Hoge3 {}' . "\n",
),
);
unset($cmtBody);
foreach ($files as $file) {
file_put_contents($file, '');
}
foreach ($sources as $source) {
foreach ($files as $idx => $file) {
if (is_array($source)) {
$code = (count($source) <= $idx ? $source[count($source) - 1] : $source[$idx]);
} else {
$code = $source;
}
file_put_contents($file, $code, FILE_APPEND);
}
}
_printMemory('start');
foreach ($files as $file) {
include_once($file);
_printMemory("after " . basename(${file}));
}
$rc1 = new ReflectionClass('Hoge1');
$rc2 = new ReflectionClass('Hoge2');
$rc3 = new ReflectionClass('Hoge3');
printf("%8d : %8s\n", strlen($rc1->getDocComment()), $rc1->getName());
printf("%8d : %8s\n", strlen($rc2->getDocComment()), $rc2->getName());
printf("%8d : %8s\n", strlen($rc3->getDocComment()), $rc3->getName());
こんな感じで実行する。(ちなみに、使用したのは仮想マシン上に構築したSientific Linux 6.2)
$ for i in 1 10 100 1000 10000 100000 ; do
echo "=== ${i} ==="
php hoge.php ${i}
done
実行結果。
=== 1 ===
init : 651048 | 786432 ( 0 | 0)
start : 656336 | 786432 ( 5288 | 0)
after foo-1.php : 657736 | 786432 ( 1400 | 0)
after foo-2.php : 659184 | 786432 ( 1448 | 0)
after foo-3.php : 660616 | 786432 ( 1432 | 0)
0 : Hoge1
29 : Hoge2
6 : Hoge3
=== 10 ===
init : 651048 | 786432 ( 0 | 0)
start : 656560 | 786432 ( 5512 | 0)
after foo-1.php : 657960 | 786432 ( 1400 | 0)
after foo-2.php : 659616 | 786432 ( 1656 | 0)
after foo-3.php : 661048 | 786432 ( 1432 | 0)
0 : Hoge1
236 : Hoge2
6 : Hoge3
=== 100 ===
init : 651048 | 786432 ( 0 | 0)
start : 658632 | 786432 ( 7584 | 0)
after foo-1.php : 660032 | 786432 ( 1400 | 0)
after foo-2.php : 663760 | 786432 ( 3728 | 0)
after foo-3.php : 665192 | 786432 ( 1432 | 0)
0 : Hoge1
2306 : Hoge2
6 : Hoge3
=== 1000 ===
init : 651048 | 786432 ( 0 | 0)
start : 679320 | 786432 ( 28272 | 0)
after foo-1.php : 680720 | 786432 ( 1400 | 0)
after foo-2.php : 705144 | 786432 ( 24424 | 0)
after foo-3.php : 706576 | 786432 ( 1432 | 0)
0 : Hoge1
23006 : Hoge2
6 : Hoge3
=== 10000 ===
init : 651048 | 786432 ( 0 | 0)
start : 886320 | 1048576 ( 235272 | 262144)
after foo-1.php : 887720 | 1310720 ( 1400 | 262144)
after foo-2.php : 1119144 | 1572864 ( 231424 | 262144)
after foo-3.php : 1120576 | 1835008 ( 1432 | 262144)
0 : Hoge1
230006 : Hoge2
6 : Hoge3
=== 100000 ===
init : 651048 | 786432 ( 0 | 0)
start : 2956320 | 3145728 ( 2305272 | 2359296)
after foo-1.php : 2957720 | 3145728 ( 1400 | 0)
after foo-2.php : 5259144 | 5505024 ( 2301424 | 2359296)
after foo-3.php : 5260576 | 5505024 ( 1432 | 0)
0 : Hoge1
2300006 : Hoge2
6 : Hoge3
まず目につくのが、パターン2とパターン3の違い。パターン3では空の「/** */」を挟み込むことによって、その上の長い「/** 〜 */」がドキュメントコメントではなくなり、切り捨てられるという動作が想像できる。
10万行というと、長過ぎるドキュメントコメントだが、例えばこれが、9個のメソッドを持つクラスで各メソッドとクラスにドキュメントコメントを5行ずつ持ち、そのようなクラスが20個ロードされるリクエストがあり、それが100リクエスト同時にくれば、ドキュメントコメントは10万行となる。
不幸なことに、ドキュメントコメントをリフレクションのためにメモリ上に保持する動作は、オフにすることができないらしい。
ということは、サーバーにアップする際にフィルタリングする仕組みを作り上げるか、IDEでの利便性を捨てて「/** 〜 */」形式のコメントを全て「/* 〜 */」に書き換えるかするしかない。
(ちなみに、上記のパターン3では、クラスのドキュメントコメントが空のものになるので、IDEでの利便性は確保できないものと推測している(未確認))
なんて迷惑な仕様なんだ‥orz