最近のディスクは高速なのであまり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);
}
});
コメント