HimaJyun「例えば、PHPを避ける」、もちろん間違った考え方なんだけど、この記事を書いた人がそう言いたくなるのも少し理解出来るPHP、いい加減でもいい加減に動いてくれて、非プログラマでもとりあえずそれっぽい感じに動く。お陰様でネット上には間違った情報だらけ、今回はちょっと目に付いた「readfile()」と言う関数の間違いを書いてみる。readfile」でググると結構良いランクの所に「メモリに溜め込んで使えない」だの書かれてあって可哀想に思う、readfileの事が。file_get_contents("hoge");」をサンプルとして載せているサイトも少なくないと思う(調べてはいないがタイトルにもある通りだが、readfileは巨大なファイルを扱える、もちろんメモリに溜め込んだりはしない。file_get_contents」なんてのは禁じ手、こいつはメモリに溜め込む、当然ながら巨大なファイルは扱えない。なんで良いの?/ダメなの?PHPしか経験のない人だと分からないだろうけど、I/O系はちょっぴり重い。(らしい、厳密に数値を測ったりした事は無い僕もそこまで詳しく踏み込んだ訳ではないのだが、echoでの出力も逐一クライアントに送っていたら効率が悪いなので、PHPにはoutput_bufferingと言う物がある。これは、echoなどの出力を全て溜めておいて、後からまとめてクライアントに送り付けると言う物。巷で良くある「readfileツカエネー」は本当はreadfileがファイルをメモリに溜め込んでいるのではなく、このoutput_bufferingの上に溜まっているって訳。実際に検証ただ、「こうなんだ!!」と提唱しただけで信じてくれる人なんて殆ど居ない。先の通り、これにはphp.iniのoutput_bufferingが関係してくるので、これのOn,Offでreadfileとfile_get_contentsの計4パターンを実際に試してみる。16.04、メモリ8G、PHP7、Apache2.4、テスト用のファイルは1Gのnullファイル。サンプルコードは以下の通り、適当だけど構わない。${mem}\n&quot;,FILE_APPEND);まずはfile_get_contentsから行こうか。output_buffering=On,file_get_contentsとりあえず絶対死ぬパターン、出力バッファが有効で、file_get_contents。結果は次の通り、開始時の消費メモリが「358912」バイト、すなわち約358kb終了時の消費メモリが「2147834528」バイト、もう桁が増えまくってヤバいかほりがプンプン漂っているが、これは2gbちょい1gbのファイルを送信するのに2gbのメモリを消費している、これはoutput_buffering分と、file_get_contents分が重ね掛けになっている事でそうなっている。このやり方は最悪、まぁ当然だよね、file_get_contentsってそう言う用途で使う物じゃないし。output_buffering=Off,file_get_contents次に、file_get_contentsがメモリに溜め込むので、今回の様な用途には適さないと言う事を確認したい。出力バッファを無効にし、file_get_contentsとechoで送信する。結果は次の通り、開始時の消費メモリが「342320」バイト、すなわち約342kbそして終了後はと言うと、「1074088360」バイト、先ほどの2gb程ではないが、やはり1gbを消費、やっぱりダメだったねっ!!このやり方もアウト、これでfile_get_contentsはこの様な用途には適さないと言う事が決まったoutput_buffering=On,readfile一般的に「readfileツカエネー」方達が陥っているパターンだと思われるもの。readfileで安全にファイルを送信しているにも関わらず、出力バッファが有効な所為で無意味になっているパターン結果は次の通り、開始時の消費メモリが「356624」バイト、もう計算するのが面倒なので手抜きだが、多分350kbくらい。終了後は......「1074098584」バイト、だいたい1gb、やはり思惑通りの結果。このやり方もアウト、残るは1つ。output_buffering=Off,readfile僕が正しいと提唱しているパターン出力バッファが無効になっており、なおかつreadfileで安全に送信している。結果は次の通り、開始時の消費メモリが「340032」バイト、多分340kbくらい。終了後はと言うと......「340128」バイト、1GBのファイルを空きメモリに問題を与えることなく送信しきった、読み通りの結果。「じゃけんoutput_buffering無効にしましょうね~」とは行かない、高速化のための機能を無効になると遅くなるのはウッキーなモンキーでも分かる。php.iniを触れないパターンも十分にあり得る訳であって、それらを踏まえたうえで3つの対処方法をご紹介する。(もっとあるかも知れない)(ちなみにfreadで少しずつ吐き出すのは本質的な解決になっていない上に気持ち悪いのでパス)output_bufferingをOffに一応書いておくがオススメはしない、php.iniにあるoutput_bufferingをOffにしよう。する事でoutput_bufferingが無効になってこれらの問題はなくなる。高速化云々のデメリットを考えればこの方法はあり得ない。output_bufferingに数値を指定On,Offしか使えないと見せ掛けて数値が指定出来るoutput_bufferingさん、実にPHPらしい。数値を指定すると、バッファの最大サイズをその値までに制限する(としか書かれていない、超過した場合に「超過分はそのまま出力」、「バッファ分を出力して再びバッファリング」、「超過した時点でバッファ分が出力され、それ以降はバッファリングされない」、の、どの挙動になるのかは分からなかったちなみに最大値は4096で、多くのサイトでも4096が推奨されている。(そして、僕も推奨している。多くの場合、PHPから巨大ファイルを送り出すにはX-SendFileかX-Accel-Redirectの様な機能を利用する事をお勧めする(使い方記事が、どうしてもPHPから送り出さなければならない場合、output_bufferingに関わらず正常に送信出来る様にしておきたい。実はこのoutput_buffering、プログラム側から無効に出来る、こんな感じで。ob_end_flush();}flush();これを、readfileなどの直前に呼び出してやれば、output_bufferingが無効化されてメモリセーフにreadfileが出来る。(ちなみに、header系の関数は先に呼び出しておかないと上手い具合に動かない)ob_end_flush()が1つのバッファリングに対してしか効かない(バッファリングは複数設定出来る(ほぼ無意味だが)ため、複数設定されている場合には複数回呼び出す必要がある)事に気付かずに「ダメだった」と勘違いしている人が多いように思える。実際はob_get_level()が0になるまでob_end_flush()を呼び出してやる必要がある。ちなみに、ob_end_clean()ではなくob_end_flush()を使おう、ob_end_clean()は今まで溜めていた内容を破棄してしまう、ob_end_flush()ならクライアントに送信される。ぶっちゃけfopen、fread、ob_flush、flush、なんて気持ち悪いループを組む意味はないと思う。バッファリングを無効にしておけば普通にreadfile1つできれいさっぱり完結するはず、少なくとも僕の知り得る限りでは。結論ここから導き出される結論は1つなんですよ、公式のマニュアル読んで下さい。前の記事でも同じ事書いてますが、PHPのマニュアルは丁寧に分かりやすく日本語化されています、これを読まないなんて言語に対する冒涜ですよ。PHPを学んでいると「ネットの情報が間違いだらけ」だと言う事を嫌でも痛感させられる。実際の所、X-SendfileやX-Accel-Redirectは早いのか?少し前に「X-SendFile、X-Accel-Redirectの使い方」と言う......続きを読む→[Java]JDK付属のJVisualVMでプロファイリングプログラムに隠れた無駄な処理を省き、真のパフォーマンスを叩き出すためには欠かせな......続きを読む→[VisualStudio]C++でもXMLドキュメントコメントを自動挿入したい!!神速の黒魔術、C++、Perlなんて屁でもない様な難読奇怪な言葉遣いで驚異的な速......続きを読む→[Bukkitプラグイン制作講座-其之一]プロジェクトの作成迄Bukkitのプラグインを開発するにあたって、まずはプロジェクトの作成を行う必要......続きを読む→PREVX-SendFile、X-Accel-Redirectの使い方NEXT日経平均株価をプログラムから取得する方法ナカのヒト:HimaJyun​散って美しいのは桜だけです、情報はまとまっていた方が美しいのです。