HHeLiBeXの日記 正道編

日々の記憶の記録とメモ‥

再開

気づけば、まる11ヶ月も放置していたらしい。
その間、いろいろあったといえばあったし、あったといえばあった(謎)。

11ヶ月前、「体調を崩している」と書いたあの頃に比べれば、相当にまともな状態。
会社に行って仕事はしていたが、生きてるんだか死んでるんだかよく分からない時期を過ごし、それを乗り越えた後の回復に時間を要し、紆余曲折を経て今に至っている。

その間も、過去に書いた自分用のメモエントリがちょっとずつブックマークされたりしているのを見てると、ちっとは誰かの役に立ってるのかな‥なんて思ったりする。

ブログに書かずに埋めてきたネタもいくつかあり、だいぶ忘れてはいるんだが、少しずつ少しずつ、思い出しながら、また以前のようなスタンスで、ちょっとずつ再開していこうと思った、そんな秋のある日‥

bytea型でバイナリデータを扱う際のワナ

やはり自称DB屋(何)としては、DBMSのことを書かないと始まらないだろうということで(謎)


PHPプログラムからPostgreSQLにいわゆるバイナリデータを放り込んでいろいろやる必要が生じたので、ちょっと調べてみると、主に以下の2通りがあるらしい。

  • bytea型の列を作るやり方
  • pg_catalog.pg_largeobjectテーブルを使うやり方

本当に巨大なデータを扱う際には、pg_largeobjectテーブルとそのためのAPIを使うのが楽なのだろうが、今回は以下の理由でbytea型を使うことにした。

  1. 格納するデータ量は大して大きくない(高々数MB)
  2. pg_largeobjectテーブルにはアクセス制御が利かないらしい
  3. pg_largeobjectテーブルの構造を見てみると結局はbytea型で、適当な大きさで分割するだけ

pg_largeobjectテーブルの構造は以下のような感じ。

test_db=# \d pg_catalog.pg_largeobject;
テーブル "pg_catalog.pg_largeobject"
 カラム |   型    |  修飾語  
--------+---------+----------
 loid   | oid     | not null
 pageno | integer | not null
 data   | bytea   | 
インデックス:
    "pg_largeobject_loid_pn_index" UNIQUE, btree (loid, pageno)

test_db=# 


さて、前置きがちょっと長くなったが、ここから本題。

事前準備

今回の検証で使った環境は以下のとおり。

以下のようにテスト用のデータベースとテーブルを作成する。

postgres=# create database test_db;
CREATE DATABASE
postgres=# \l
                                         データベース一覧
   名前    |  所有者  | エンコーディング |  照合順序   | Ctype(変換演算子) |      アクセス権       
-----------+----------+------------------+-------------+-------------------+-----------------------
(中略)
 test_db   | postgres | UTF8             | ja_JP.UTF-8 | ja_JP.UTF-8       | 
(4 行)
postgres=# \c test_db
psql (8.4.18)
データベース "test_db" に接続しました。.
test_db=# CREATE TABLE test1(
test_db(#   id SERIAL PRIMARY KEY,
test_db(#   data BYTEA NOT NULL
test_db(# );
NOTICE:  CREATE TABLEはシリアル列"test1.id"用に暗黙的なシーケンス"test1_id_seq"を作成します。
NOTICE:  CREATE TABLE / PRIMARY KEYはテーブル"test1"に暗黙的なインデックス"test1_pkey"を作成します
CREATE TABLE
test_db# 

最初にやったこと

以下のようなPHPプログラムを作って、データの投入と取り出し・表示を行った。

<?php

$originalData = "Hello World\n\tHello PostgreSQL\nはろーわーるど\n";

$con = pg_connect("dbname=test_db user=postgres");

$data = pg_escape_bytea($con, $originalData);
$res = pg_query_params($con, 'INSERT INTO test1(data) VALUES($1)', array($data));

$res = pg_query("SELECT * FROM test1");
while (($row = pg_fetch_assoc($res))) {
    var_dump(array($row['id'], pg_unescape_bytea($row['data'])));
}
pg_free_result($res);

pg_close($con);

すると、実行結果は以下のような感じ。

array(2) {
  [0]=>
  string(1) "1"
  [1]=>
  string(127) "Hello World\012\011Hello PostgreSQL\012\343\201\257\343\202\215\343\203\274\343\202\217\343\203\274\343\202\213\343\201\251\012"
}

‥あれ、エスケープされたものが戻ってない‥
そこで、エスケープ直後のデータを出力してみると‥

Escaped Data: Hello World\\012\\011Hello PostgreSQL\\012\\343\\201\\257\\343\\202\\215\\343\\203\\274\\343\\202\\217\\343\\203\\274\\343\\202\\213\\343\\201\\251\\012

なんか、バックスラッシュが多い気がする。

pg_query_paramsを使わない形にしてみる

さっきのプログラムをちょっと変えて、以下のようにする。

<?php

$originalData = "Hello World\n\tHello PostgreSQL\nはろーわーるど\n";

$con = pg_connect("dbname=test_db user=postgres");

pg_query($con, 'DELETE FROM test1');
$data = pg_escape_bytea($con, $originalData);
//$res = pg_query_params($con, 'INSERT INTO test1(data) VALUES($1)', array($data));
$res = pg_query($con, "INSERT INTO test1(data) VALUES('{$data}')");

$res = pg_query("SELECT * FROM test1");
while (($row = pg_fetch_assoc($res))) {
    var_dump(array($row['id'], pg_unescape_bytea($row['data'])));
}
pg_free_result($res);

pg_close($con);

実行結果。

array(2) {
  [0]=>
  string(1) "5"
  [1]=>
  string(52) "Hello World
        Hello PostgreSQL
はろーわーるど
"
}

今度はうまくいった。

結局‥

調べてみると、pg_escape_bytea関数は、クエリ文字列に直接埋め込むのに適切な形にエスケープするらしい。
PHPのドキュメントを見ると、以下のようなことが書いてある。


The reason pg_unescape_bytea() do not exactly reproduce the binary data created by pg_escape_bytea() is because the backslash \ and single quote ' are double escaped by the pg_escape_bytea() function. This will lead to image seems corrupted when retrieve from the bytea field. The proper way to escape&unescape a binary string into a PG bytea field as follow:

<?php 
$escaped_data = str_replace(array("\\\\", "''"), array("\\", "'"), pg_escape_bytea($data)); 
/* and later unescape the escaped data from the bytea field with following to get the original binary data */ 

$original_data = pg_unescape_bytea($escaped_data)); 
?> 

more details at: http://archives.postgresql.org/pgsql-php/2007-02/msg00014.php

なるほど、確かにバックスラッシュとシングルクォートの二重エスケープを外してやれば、pg_query_paramsを使った場合でも期待通りの結果が得られる。

でも、それだったら、「pg_escape_bytea⇔pg_unescape_bytea」の対称性を保持する意味でも、

  • クエリ文字列に直接埋め込む(pg_queryを使う)場合は、pg_escape_byteaでエスケープした後にpg_escape_stringやpg_escape_literalでエスケープする。
  • パラメータを分離する(pg_query_paramsを使う)場合は、pg_escape_byteaでエスケープした結果をそのまま使う。

っていう形の方が分かりやすい気がするけどな‥

よかった探しリース

←左手よかった探しリース右手→
今年も「よかった探しリース」に参加させていただきます。

(ちょっとの間だけネガティブになるけど)今年は、「1月⇒半死」「2月⇒全死」「3月⇒全死」「4月⇒微生」「5月⇒半生」「6月⇒半死」「7月⇒全死」「8月⇒半死」「9月⇒半死」「10月⇒超死」「11月⇒爆死」とまぁ、酷い状況だったわけで‥12月がどうなるんだろうという心配は多分にあるわけだが‥

それでも探せばきっと何かがあるはず。今年はほんとにゆっくり思い出しながら書きます。

  • それでも家族が大過なく過ごせたこと
    • 私自身はだいぶ体調を崩してますが‥
  • 転職してから2年持った‥もとい経った(謎)
  • 一人カラオケの楽しさを覚えてしまった
  • 「ドクターX」(テレビ朝日系)が面白い(現在進行形(謎))
  • 歌手「家入 レオ」がお気に入りの一人に

牛の歩み(何)で少しずつ追加していきます。

(2012/12/15)
これまでに4個くらい追加。
今年はほんとに出てこない‥

「○○ができる」ということ

ここのところ、技術的ネタをまとめる(あらゆる意味での)余裕がないので、なんとなく思ったことを書いてお茶を濁してみるテスト(何)。

普段思っていてもなかなか言い出せない、「こいつを一人前の○○に鍛え上げてくれ‥1年で!」という無茶振りに対する「できるか!!」の一言。(もののたとえです、細かいことは気にしないように(謎))
そもそも一人前って何だよ、とか、一人前ってそんなに安いものだったのかよ、とか(以下略(謎))。

何かを一人前にできるようになるということは、つまりその道のプロになるということだと思うわけだよ。
そもそも自分だって発展途上なのに、人のことにまでかまってられるかっていうのがひとつ(指導などを放棄するという意味ではない)。
もうひとつ、人は、何かができるようにするということは、知識や操作手順、作業手順などのようなものを指してそれを習得させること、そのように勘違いしている人が少なからずいるような気がするということ。

例えば、自動車の運転

まさか、自動車運転免許を持っている人の中で、「初心者マーク」の意味が分からない人などいないだろう‥と信じたいところだが‥
一般的には、教習所に通って車の操作手順や交通規則などの知識を身につけ、教習所の課程を修了。その後、学科試験を受けて合格すれば運転免許証が発行される。
ただ、そんな知識や表面的な技術を身に付けただけでは、まだまだ危なっかしい。なので実際の路上で1年の間、初心者として扱われるわけだが、それを経てもなお足りないものがある。「経験」がそれだ。
運転免許証が発行されるまでにやっていることは、全員が守るべき「ルール」を叩き込むこと。これによって、路上で生き延びるための最低限の技術を身につける。
初心者マークが取れるまでにやっていることは、「危険」を知ること。さまざまな「危険」に遭遇すること。それでも「ルール」が叩き込まれていれば、最悪の事態は避けられるだろう。
そして、初心者マークが取れてなお身に付かないものが、危険を未然に避けるための「予見する力」。これは圧倒的な経験によってしか身に付かない(というのが私の持論)。

「あの車、動きがちょっと怪しいな、近づかないほうがいいかも」「あの横道、なんとなく何か飛び出してきそうだな、ちょっと離れておこう」‥そんなものは、教えて身に付くものではない。今までに自分がどんな危険に遭遇したか、それはどんな場面だったかということを積み上げることによって、初めて予見ができるようになるのである。
そうなって初めて「一人前」と言えるんじゃないだろうか。そんなの、1年じゃあ到底身に付かないし、人に教わるだけで身に付くものでもない。

例えば、医者

なぜ「医者」と思われるかもしれないが、それはドラマ「ドクターX」の影響‥という話は置いておくとして‥

医者の世界がどんななのかは知らないが、まぁ、最初は何も知らない一人の人がいるのだろう。
その人が、人体の組成やら治療の道具を使う技術やら薬の知識やらを習得し、手術や治療などの実施手順を覚え、いずれは一人の医者として実際の患者と相対するのだろう。
その時点で圧倒的に足りないものが「経験」である。
例えば外科手術、「メスで皮膚や肉を切り、患部を切り取って摘出し、皮膚などを縫合する」‥と手順を示すと簡単そうに見えるが、体は人それぞれ違うし、患部の状態も当然違うだろうから、そういった違いに臨機応変に対応できなければいけないし、場合によっては、皮膚を切って開けてみて初めて判明する衝撃の病状というのもあるだろう。そんな場合でも冷静に対処できる力を身につけるには、さまざまな経験を積んでいくしかない。
そんなの、どうしたって1年やそこらで身に付くものではない。

例えば、ITエンジニア

そこに何も知らない一人の人がいるとする。

最初は、「とりあえずこれはオマジナイだよ、こう書けば"Hello World!"って出力されるよ」から始まり、いろんなロジックを書くための構文を覚え、いろんなアルゴリズムやデータ構造を覚え、"オマジナイ"の意味も理解し、そうして、ある程度のプログラムが書ける人ができあがる。
さらに、与えられた設計を元にプログラムを書く力を身に付け、いろんなライブラリを探し使いこなす力を身に付け、そうしてさらに高度なプログラムが書ける人ができあがる。
この辺まできたら、その人のことはプログラマって呼んでいいのかな‥よく分からない。たぶん「経験」が足りないからなのだろう。
プログラムを書いて捨てる(違)だけならそれなりの人でもできる。既存のプログラムの挙動を見て、解析し、くさそうな所をすばやく見つけ出す、いわゆるコードレビューであったりトラブルシューティングであったりするわけだが、そういうことができるようになって初めてちゃんと「プログラマ」って呼んでいいんじゃないかなと思う。
それはさまざまな経験を積んでいくしかない。数をこなすしかない。(と私は思っている)
そんなの、1年やそこらで身に付くものではないし、人に教わるだけで身に付くものでもない。


そこに何も知らない一人の人がいるとする。

最初は、lsとかcpとかmvとかのファイル操作コマンドを覚え、viエディタを使えるようになり、プロセスの状態を見たり、ネットワークの状態を見たり、そんなことをするためのコマンドやら何やらを覚えていく。そうして、ある程度サーバー機の状態を知ることができる人ができあがる。
さらに、与えられた情報を元に、OSを入れたり、サーバーソフトウェアを入れたり、各種設定を要求どおりに行ったりする力を身に付け、そうしてサーバーを構築できる人ができあがる。
この辺まできたら、その人のことはサーバーエンジニアって呼んでいいのかな‥よく分からない。たぶん「経験」が足りないからなのだろう。
手順どおりにやるなら、手順書があればできるし、今はネット上にいろんな情報が転がっている時代。検索してちょっと試せばそれなりにサーバー構築はできてしまう。
運用中のサーバーに何かトラブルが起こったときに、原因と思われる箇所の見当をつけ、原因を探り、問題を見つけ出して対処する、ある程度は手順に従って解決可能ではあるが、経験と勘も非常に重要になってくる。急ぎで復旧させなければならない場合などはなおさらだ。
それにはさまざまな経験を積んでいくしかない。場数を踏むしかない。
そんなの、1年やそこらで身に付くものではないし、人に教わるだけで身に付くものでもない。

で‥

いろんな人の仕事ぶりを見ていると、少なからず「手を動かさない人」が存在する。
もちろん、仕事は遊びではないので、のんびり実験している暇なんてない場合もあるだろう。そういうときには、ネットで調べて得た情報をある程度検証して適用するということも必要である。
ただ、それだけになってしまうと、いざというときの対応力は身に付かないと思うのである。
ディスプレイを見ながらウンウンうなるよりも、さっさとチョンプロを作ってどうなるかを試したほうが、デバッグ出力を入れてみて状態を確認したほうが、たぶん解決は数倍数十倍早い。
若者も年寄りも、おじさんもおばさんも、お兄さんもお姉さんも、手を動かせ、エンジニアたちよ(謎)。



担(誰)「結局、使えない人をさっさと切り捨てる言い訳をしているだけじゃん」
私(誰)「うん、そうだよ」
担(誰)「えっ!?」
私(誰)「えっ!?」

ノートPCのお掃除

先月は職場滞在時間400時間超えという訳のわからない領域に踏み込み、ついにブログを1件も書けなかったというひどい状況。ひどい状況なのは今もあまり変わらないが‥って愚痴を言いに来たんじゃないや‥

閑話休題

何かのスイッチが入り、新ノートPC(Let's Note CF-SX2)を購入。それまで使っていたのが2007年5月21日に購入して同月26日に届いたLet's Note CF-W5。もう5年半近くになるのだな、と。
まだ次の活躍の場が待っているのだが、その前に、ということでちょっとお掃除してみた。
さすがに本格的な分解清掃は怖いので、キーボード周りのみの清掃。


キーの配列を間違えないようにするために、清掃開始前に1枚。

見た目には分かりにくいけど、実は‥


キートップを全部外したところ

思った以上に埃が溜まってました。
パンタグラフの向きがキーによって違うのにちょっとびっくり。


外したキートップ

よーく見ないと分からないですが、キートップの裏にも埃が結構付いてます。
経年劣化するものですね、外したときだと思いますが、何枚かに「ひび」が入ったり、爪が割れたりしてしまいました‥
一緒に写っている埃は、キートップを外す前にブラシで掃きだしたもの。


清掃後

埃などを掃ってきれいになりました


出てきた埃

こんなに溜まるんだな、と‥


キーをはめて元通り

ひび割れがひどかったものは、木工用ボンドで被膜を作りました。崩壊を少しでも食い止められれば、と‥
写真には写りようもないですが、HDD内の初期化も済んでいます。


しばしの暇のあと、第二のお役目が待っていますが、ひとまずはお疲れ様。

メモリの使用量を気にしながらサーバーを再起動

今月は、ほぼ死に状態で、何もできなかったので、せめてものしょうもない内容で締めようと思う(謎)。

ってな訳で(謎)、あるサーバーのメモリ使用量が気になるが、張り付いている訳にもいかない、そんなときのひとつの手段として、定期的に再起動するようにcron設定をしておくというのはよくあるやり方。
ただ、必要のないときにまでわざわざ再起動することはないじゃん、というのもあるし、どのくらいの周期で再起動をすればいいのかが時間とともに変わってくるということもある。結局は張り付いていなきゃいけないのかということを思ったときにふと思いついたやり方。

他にもっとちゃんとしたやり方があるような気もするがな。まぁ、死にかけの頭の中でふと思いついた程度のことなので、自分へのメモ程度にということで(謎)。


まずは、cronの設定。これは定期的に状況を監視するという意味で、周期はちょっと短めに設定しておく。

*/15 * * * * /root/scripts/httpd-graceful-with-check-memory.sh 2&>1 >> /dev/null

ファイル名から察するに(謎)、中身はApache httpdのgracefulらしい。
‥とまぁ、それはどうでもいいとして。
httpd-graceful-with-check-memory.sh の中身。

#! /bin/bash

# logger: 任意のメッセージをsyslogに書き込むためのコマンド
# loggerコマンドのパスは環境によって異なるので確認すること!!
#logger=/usr/bin/logger
logger=/bin/logger

memUsage=$(($(free | grep '^[-]/[+] buffers' | awk '{printf("100
*%d/(%d+%d)", $3, $3, $4);}')))

if [ ${memUsage} -ge 80 ]; then
    /etc/init.d/httpd graceful
    printf "Memory usage: %3d%%, so servers are restarted. (status: %d)\n" ${memUsage} $? | ${logger} -t httpd-checker -i
fi

ここでは、freeコマンドの出力を使用している。
ローカルでsnmpdが動いているなら、そこに問い合わせた結果を使ってもいいし。
また、再起動記録の出力先にsyslogを使ってみている。

freeコマンドは次のような出力を吐き出す。

             total       used       free     shared    buffers     cached
Mem:        508864     503036       5828          0      60940     228064
-/+ buffers/cache:     214032     294832
Swap:       397304       1508     395796

ずいぶんと貧弱だが、VMware上で動いている、512MBほどのメモリを割り当てた仮想マシンなのでこんなもんです。
freeコマンドの出力なんかは以下のサイトを見るといいかもしれない(のでここでは省略)。

大体、上記の出力例では実質42%ほどを使っていることになる。


あまり細かにメモリ使用量を調べて判定、とかやりだすと、そっちに余計なコストがかかって本末転倒なので、逆に上記のような手抜き簡単なやり方で十分だろうと思う。


機会があったら試してみたいがな(ぉ

メモリ喰らいの仕様

最近、気になる情報を目にしたらしい。

よく読んでみると、「ReflectionClass::getDocComment()」に関係する仕様らしい。

怖いので、3パターンほど試してみた。(コードがちょっと分かりづらくて申し訳ないものだが)実際に動かしたコードは後述する。

今回、実験に使用するコードでは、以下のことを行なう。

  1. 3パターンのPHPコードを動的に生成し、ファイルに保存する。
  2. それらのファイルを、順にinclude_onceする。
    • 実際に試してもらうと分かるが、include/include_once/require/require_onceのどれでも結果は変わらない。
  3. 増加したメモリ使用量を出力する。
    • memory_get_usage()と、念のためmemory_get_usage(true)の値の両方を出力。

3パターンのPHPコードとは以下のようなもの。

  • パターン1:「/* 〜 */」形式のコメントのみ
<?php 
/* 
@param hoge00000 00000
ここをどんどん増やしていく
*/
/*  */
class Hoge1 {}
  • パターン2:「/** 〜 */」形式のコメントの後に「/* */」を挟み込んでみる
<?php 
/**
@param hoge00000 00000
ここをどんどん増やしていく
*/
/*  */
class Hoge2 {}
  • パターン3:「/** 〜 */」形式のコメントの後に「/** */」を挟み込んでみる
<?php 
/**
@param hoge00000 00000
ここをどんどん増やしていく
*/
/** */
class Hoge3 {}

以下が、実際に動かしたコード。
各パターンでの、各クラスのドキュメントコメントも取得して、長さを出力してみている。

<?php

function _printMemory($msg) {
    static $pre1 = -1; // emalloc()が使用するメモリ(前回呼び出し時の値)
    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);

// clear contents of files.
foreach ($files as $file) {
    file_put_contents($file, '');
}
// put generated contents to files.
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