最近のディスクは高速なのであまりIO Waitを意識することは少なくなりましたが、書き込み速度の遅いディスク(書き込みのコマンドのコストが高い)に何度も書き込みを実行すると、書き込みのトータルの時間が長くなる場合があります。
この投稿では、PHP版の簡単な「なんちゃって」BufferedWriterクラスを書いてみました。
通常であれば単純にfile_put_contentsメソッドを使って下記のように書けば十分です。
file_put_contents($filename, $chunk, FILE_APPEND|LOCK_EX);
しかし、大量のデータを何度も書き込む場合に、特にパフォーマンスの観点から下記の問題が出てきます。
- file_put_contentsはfopenによるファイルオープン、書き込み、クローズを実行するので手軽ではありますが、書き込みのオーバーヘッドは高くなります。
- file_put_contentsに限ったことではないが、メモリにある程度バッファしてから書き込むことで、書き込みの実行回数を減らすことでパフォーマンスを高めることができる(例: JavaのBufferedWriter)。
というわけで、上記の問題を解決すべく、PHP版「なんちゃって」BufferedWriterクラスを書いてみました。
<?php class BufferedStreamFileWriter { private $path; private $bufferSize = 0; private $bufferLimit; private $buffer = ''; // constructor内でfopen public function __construct($path, $bufferLimit, $mode='a') { $this->path = $path; $this->bufferLimit = $bufferLimit; $this->handle = fopen($path, $mode); } // このメソッドが何度も呼ばれる想定。 // 設定してbufferLimitに到達したらディスクの書き込み public function write($string) { $size = strlen($string); $this->bufferSize += $size; $this->buffer .= $string; if($this->bufferSize > $this->bufferLimit) { $this->flush(); } } // 全部データを書き込んだら、closeを必ず呼ぶこと (finallyブロックに入れることを想定) // closeを呼ばないbufferプロパティに残ったデータは書き込まれないまま終了してしまうので要注意。 public function close() { $this->flush(); fclose($this->handle); } // ディスクへの書き込みメソッドfputs利用 public function flush() { if($this->buffer) { fputs($this->handle, $this->buffer); $this->bufferSize = 0; $this->buffer = ''; } } // BufferedStreamFileWriterのインスタンス生成、closeの処理を実行するショートカットメソッド。 // $callbackに実際の書き込み処理を渡す。 public static function writeAndClose($filePath, $bufferLimit, callable $callback) { $writer = new BufferedStreamFileWriter($filePath, $bufferLimit); try { $callback($writer); } finally { $writer->close(); } } }
下記は使い方の例になります。
// 基本的な使い方 $filePath = '/tmp/test.txt'; $writer = new BufferedStreamFileWriter($filePath, 10); try { // 何度も書き込み for($i = 0; $i < 1000; $i++) { $writer->write($i); } } finally { $writer->close(); } // writeAndCloseメソッドを使った場合 $filePath = '/tmp/test.txt'; BufferedStreamFileWriter::writeAndClose($filePath, 10, // 何度も書き込み function(BufferedStreamFileWriter $writer) { for($i = 0; $i < 1000; $i++) { $writer->write($i); } });
コメント