2010年09月09日(木)銀の指、炎の鍵。

前回までのあらすじ。「俺はPHPでサイトを構築している。PHPにおけるループの種類に関して適宜適切な選択をしていきたいと考えている。その為に修正中」

以上。そして前回からの続き。とまむさんはマイノリティなので着眼点が普通とは異なっていて正直検索をしていても目的の情報にたどり着くと言う事が殆ど無い。
「なんか違うなぁ」と口にする事が時々ある。じゃあ自分でやりましょう。となる。今度は「自分がやった内容が他人から見ると違和感」と言うコンフリクトに繋がる。

まぁそれはとまむさんがマイノリティである以上しょっちゅうなんで。コンフリクトが発生する理由は考えた所で時間の無駄。さっさと捨てて次へ行く。ほんの少し先の未来。

…そう言う訳で「他人のブログなどに書いてある速度比較のやり方」というのは「どうでもいい内容をループさせてじゃあどのぐらいの速度が掛かるのでしょうか?」
で終わってる。そんなに単純なループで終わる訳が無いだろうに。もっとこうループの最中に具体的な処理を加えている事の方が殆ど。実用とはそぐわない。

この「実用とはそぐわない」「実戦に行ってどうなのか」と言う概念が抜け落ちているのはやはり2バイト思考だな。と思う事が最近は良くある話。

1バイト思考:1歩跨げばよその国。他国との思い違いを防ぐ為に文法からルールの取り決めから割とこと細かく定められている。そうしたロジック的外国人思考。
2バイト思考:基本はおおらか。海に囲まれている日本は他国からの進入が思いの外難しい。そうした概念による以心伝心の細かい事を考えない日本人思考。

どちらが良い悪いではなく「それぞれの感覚の良い所を両輪として回していくいわば3バイトによるユニコード(多言語対応)思考」と言うのが俺のやり方なんで。
だからこその「ネオ日本人」発言している訳ですが。ある種の枷みたいな物か。まぁとまむさん超ドSなんですけど。そんなとまむさんの性癖は横においといて。

[ どっちのPHPショー(2)ループ速度比較 ] PHPにはいくつかのループが用意されています。一般的なforやwhileループ、配列値を順にセットするforeachループなどが
それです。果たしてループにおいてそれぞれ速度差が出るものなのかを検証してみたいと思います。(そろそろホンキ出す 2009/11/11 18:04:07)

ひとまずそこでのエントリーに上げられていたサンプルスクリプトで「単純にループした場合の速度は」検討する事が可能。俺が重要に思っているのは
ループの速度も然る事ながら、「スクリプトのソースの美しさ」も考慮に入れている。つまり「ループするのに必要な事前準備のソースが汚くなる」のは歓迎しないんで。

まずは速度。次にコード。それを見て、「適材適所」として入れていく。以上。「実験をやって満足」で終わる日本人。「それがどう繋がっていくのか」と言う概念が脆弱。

10回ループ「0.0000898838」 → 100回ループ「0.0001039505」 → 1000回ループ「0.0001659393」 (for)
10回ループ「0.0000219345」 → 100回ループ「0.0000579357」 → 1000回ループ「0.0004100799」 (foreach)
10回ループ「0.0000131130」 → 100回ループ「0.0000200271」 → 1000回ループ「0.0000801086」 (while)

結果はこうなった。whileがどの回数でも最も速く、ループ回数が少なければforeachの方が速くなり多くなればなるほどforの方が最終的には速くなる。
forとforeachの順位が入れ替わるのは現在とまむさんが使っているサーバー上では「200回ループ」の辺りが交差する回数となっている。

forは初速から遅めだが回数を重ねてもスピードがあまり落ちないステイヤータイプ。トウカイトリックの様なループ。
foreachは初速は速めだが回数を重ねるとスピードが落ちてくるスプリントタイプ。ワンカラットの様なループ。
whileはどのスピードでも最も速い。サラブレッドを更に超えた様な新種と考えるとしっくり来るかもしれないんだが扱うのが難儀なじゃじゃ馬の様なループ。

こうなる。こうやって他の事で例える方が分かりやすい。如何にして説明を咀嚼して平易にするか。それが重要。「それをする」と言う配慮は2バイト思考の側に近い。

「速度」は分かった。問題なのは次。「スクリプトのソースの美しさ」はどうなのか。簡易に書けるのか。うっかり無限ループしちゃった、となりやすいのかどうか。
スクリプトの中に組み込むとしてその組み込んだ内容が雑多になりやすいのかなりにくいのか。そうした「実用上」の面も合わせて考慮する必要があると考える。

「ループの始点と終点を指定する必要がある(それも雑多に)」 と言うデメリットを所持しているのがforとwhileの弱点。foreachはarray_sliceを使う事で
「プラスプラス」なアレ。を「forの場合はループ開始時に」「whileの場合はループ終了時」にそれぞれつける必要がある。どうも俺はそれが生理的に苦手なんで。

「プラスプラス」とか書けばどこかのコンマイ的ギャルゲーの様相を呈してくる様な感じでもありますが。こう言う「言い換え」「例え話」を使う事。実験をやって満足。
で終わるのではなく、説明を平易に行えてこそ意味があると俺は思っている。ループしたい内容に「余計な装飾をつけなくてもいい」と言うのはforeachの隠れたメリット。

「速度:while最強」「ソースの美しさ:foreachが俺の好み」 …ここまでは分かった。問題なのは「どこでどのループを使うのか」と言った「適材適所」の話となる。

基本はwhileを使う。最も高速だから。但しループの始点と終点を指定する必要がある。「最初から最後まで」「最初から途中まで」「途中から最後まで」は指定も楽。

問題なのは 「途中から途中まで」 ここだけが難しくなってくる。その「途中」に関しても実は固定で「10件目から30件目まで」の様なやり方ならそう指定すればいい。
問題なのは 「可変する場合には」 それらの内容がきつくなる。「10件目を指定し、そこから20件先の内容までを読み込む」「最初の指定は好きな位置から可能」。

と言う場合にはwhileだと苦しくなる。GETを使って例えば「10」と言う数字を指定したとする。そこから10件先なので 「(指定した数字) < 20」 とはならない。
これでは「10件目から20件目までの10件」しか出力されない。「(指定した数字) < 20+(指定した数字)」これだと指定した数字がwhileループで増える度に
後ろの数字も増えると言う事で終わらなくなる。「10 < 20+10」 → 「11 < 20+11」と言うある意味永遠の追いかけっこの様な状態へと続く。

解決するとすれば「(指定した数字)」を別の変数にコピーした上で「コピーした変数はループしても増えない」事を利用するしかない。「その」記述がだるくなる。

「(指定した数字) < 20+(指定した数字をコピーした、ループを繰り返しても増える事がない数字。指定した数字と同じ数)」 これで件数を読み込む事が出来る。

…そして。「指定した数字」が必ずあるなら上記の内容でも構わない訳だが「指定した数字が必ずあるとは限らない」訳で。その場合にどう対処するのか。
「GETで数字の指定が無い場合には1からスタートする」と言う注釈を書けばいいんだろうが「それを書いた分またソースが余計に汚くなるよ…」となる。

「指定した数字が無ければ開始する数字は幾つから」「指定した数字があれば開始する数字はそれで」「指定した数字をコピーする別の変数を書く」
「ループの始点指定も終点指定も長くなる」「無限ループに陥りやすいwhileの『1番最後にプラスプラスを書くあの書き方」 と言うのがwhileの弱点。

そこをカバーする事が出来るのがforeach。途中から途中に限定した出力でarray_sliceを使えばシンプルな記述で抽出する事が出来る。
array_slice(配列、始点、始点から何件先まで出力するのか)を書いてforeachに渡す事で「ループ開始時に始点と終点を指定する個人的には好きになれない
あの記述の内容」「余計なプラスプラスを終端につける必要がある」と言うwhileを使う際の個人的デメリットが無くなる。「ソースの美しさ:foreachが俺の好み」だと。

指定した件数を途中から途中まで出力する。その際に「100件出力」「1000件出力」などと言う話は滅多に聞かない。途中から途中までを抽出すると言う事は
せいぜい20件とかあるいは50件とか。何かしらのリストを出力する際に100件を1回で出力してじゃあ今度は人間の側が問題になってそれを瞬時に把握する事
あなた出来ますか?となる。無理だろ。「(人間の)処理能力」「1画面に出力して閲覧する事が出来る」となるとせいぜい20件、多くても40件程度。と俺は考える。

以上、ループに関してまとめると「大半の場合でwhileループを使う」「部分部分でforeachを使った方がしっくり来るケースもある」となる。

そして。「PHPの高速化」に関してはループとは別に「配列をどう収納するか」も重要になってくる。ログファイルを1行ずつ配列に収納していく際に
file関数を使って一気に読み込むのか。fopen関数を使ってwhile→feof→fgetsで1行ずつ最後まで読み込むのか。「それ」は一体どちらが早いのか。

正直「殆ど同じ速度」だと俺は思っている。「そこ」に関する具体的な速度比較のソースが見つからない。file関数は一気に読み込むのでメモリを多く使う。
と言う話も聞いている訳だが「一気に読み込む」のと「1行ずつ最後まで読み込む」のとでそんなにメモリの消費量に対する違いがあるのか?と言うその点は
サーバー管理者でないと使用しているメモリの量なんて分からないのでとりあえずの所は「多分そうなんだろうな」と言う意見にとどめておく事しか出来ない。

「ファイルバイト数が小さい、行数が少ない場合にはfileで読み込み」「アクセス解析のログファイルの様にファイルバイト数が多くなる可能性がある。
あるいは自分の手ではなく人のアクセスによってファイルバイト数が増えていくと言う他動的手段。に関する場合はfopen→while→feof→fgets→fcloseを使う」とする。

細かい事を書けばwhileの終端はcountで調べる事が出来る。但しwhileの終点をcountで直接指定するとループの速度が遅くなる。予めcountの値を変数化し
それをwhileの終点に入れておいた方がいい。「それ」の流れに関するソースが美しくない。「feof」は終点まで一気に調べてくれる。その違いがある。

「countで調べる事もなく、例えば『最初の行から数えて3行目まで!』と言う様な決め打ちをする場合にはwhileの方が便利。そうでなければfeofの方が便利。

2010年09月09日(木)20時26分07秒