[PHP]set_file_buffer関数を完全解説!ファイルバッファリングを設定する方法

PHP

こんにちは!今回は、PHPの関数であるset_file_buffer()について詳しく解説していきます。ファイル書き込みのバッファリング動作を制御できる関数です!

set_file_buffer関数とは?

set_file_buffer()関数は、ファイルストリームの書き込みバッファのサイズを設定する関数です。

重要: この関数はstream_set_write_buffer()の別名(エイリアス)です。PHP 8.0以降ではstream_set_write_buffer()の使用が推奨されています。

バッファリングを制御することで、ファイル書き込みのパフォーマンスを最適化できます!

基本的な構文

set_file_buffer(resource $stream, int $size): int
// または
stream_set_write_buffer(resource $stream, int $size): int
  • $stream: ファイルポインタリソース
  • $size: バッファサイズ(バイト単位)、0で無バッファリング
  • 戻り値: 成功時は0、失敗時は-1

バッファリングの種類

// フルバッファリング(デフォルト)
// バッファが満杯になるか、fflush()が呼ばれるまで書き込みを保留
$fp = fopen('file.txt', 'w');
stream_set_write_buffer($fp, 8192);  // 8KB バッファ

// 無バッファリング
// 即座にディスクに書き込む
stream_set_write_buffer($fp, 0);

// ラインバッファリング(標準出力など)
// 改行が来るまでバッファリング
// (ファイルでは通常使用されない)

基本的な使用例

シンプルなバッファ設定

// ファイルを開く
$fp = fopen('/tmp/output.txt', 'w');

// バッファサイズを4KBに設定
$result = stream_set_write_buffer($fp, 4096);

if ($result === 0) {
    echo "バッファ設定成功\n";
} else {
    echo "バッファ設定失敗\n";
}

// データを書き込む
fwrite($fp, "Hello, World!\n");

// ファイルを閉じる
fclose($fp);

無バッファリング(即座に書き込み)

$fp = fopen('/tmp/log.txt', 'a');

// バッファを無効化(即座に書き込み)
stream_set_write_buffer($fp, 0);

// 各書き込みが即座にディスクに反映される
fwrite($fp, "Log entry 1\n");
fwrite($fp, "Log entry 2\n");
fwrite($fp, "Log entry 3\n");

fclose($fp);

大きなバッファサイズ

$fp = fopen('/tmp/large.txt', 'w');

// 64KBの大きなバッファ
stream_set_write_buffer($fp, 65536);

// 大量のデータを書き込む
for ($i = 0; $i < 1000; $i++) {
    fwrite($fp, str_repeat("x", 100) . "\n");
}

// バッファを明示的にフラッシュ
fflush($fp);

fclose($fp);

バッファとfflush()の組み合わせ

$fp = fopen('/tmp/progress.txt', 'w');

// 8KBバッファ
stream_set_write_buffer($fp, 8192);

for ($i = 1; $i <= 100; $i++) {
    fwrite($fp, "Processing item {$i}\n");
    
    // 10件ごとに強制的にディスクに書き込み
    if ($i % 10 === 0) {
        fflush($fp);
        echo "Progress saved: {$i}%\n";
    }
}

fclose($fp);

実践的な使用例

例1: ログファイルライター

class LogWriter {
    private $fp;
    private $bufferMode;
    
    /**
     * ログライターを初期化
     */
    public function __construct($filename, $bufferMode = 'default') {
        $this->fp = fopen($filename, 'a');
        
        if ($this->fp === false) {
            throw new Exception("Failed to open log file: {$filename}");
        }
        
        $this->bufferMode = $bufferMode;
        $this->setBufferMode($bufferMode);
    }
    
    /**
     * バッファモードを設定
     */
    private function setBufferMode($mode) {
        switch ($mode) {
            case 'unbuffered':
                // 即座に書き込み(重要なログ向け)
                stream_set_write_buffer($this->fp, 0);
                break;
                
            case 'small':
                // 小さいバッファ(4KB)
                stream_set_write_buffer($this->fp, 4096);
                break;
                
            case 'large':
                // 大きいバッファ(64KB)
                stream_set_write_buffer($this->fp, 65536);
                break;
                
            case 'default':
            default:
                // デフォルト(8KB)
                stream_set_write_buffer($this->fp, 8192);
                break;
        }
    }
    
    /**
     * ログを書き込み
     */
    public function write($level, $message) {
        $timestamp = date('Y-m-d H:i:s');
        $logEntry = "[{$timestamp}] [{$level}] {$message}\n";
        
        fwrite($this->fp, $logEntry);
    }
    
    /**
     * バッファを強制フラッシュ
     */
    public function flush() {
        fflush($this->fp);
    }
    
    /**
     * クローズ
     */
    public function close() {
        if ($this->fp) {
            fclose($this->fp);
            $this->fp = null;
        }
    }
    
    /**
     * デストラクタ
     */
    public function __destruct() {
        $this->close();
    }
}

// 使用例
echo "=== ログファイルライター ===\n";

// 無バッファモード(重要なログ)
$criticalLog = new LogWriter('/tmp/critical.log', 'unbuffered');
$criticalLog->write('CRITICAL', 'System failure detected');
$criticalLog->write('CRITICAL', 'Emergency shutdown initiated');
$criticalLog->close();

// デフォルトモード(通常のログ)
$appLog = new LogWriter('/tmp/app.log', 'default');
for ($i = 1; $i <= 100; $i++) {
    $appLog->write('INFO', "Processing request #{$i}");
    
    if ($i % 20 === 0) {
        $appLog->flush();  // 20件ごとにフラッシュ
        echo "Flushed at {$i}\n";
    }
}
$appLog->close();

// 大容量モード(高頻度のログ)
$debugLog = new LogWriter('/tmp/debug.log', 'large');
for ($i = 1; $i <= 1000; $i++) {
    $debugLog->write('DEBUG', "Debug message #{$i}");
}
$debugLog->close();

echo "ログ書き込み完了\n";

例2: データエクスポートシステム

class DataExporter {
    private $fp;
    private $recordCount = 0;
    private $flushInterval;
    
    /**
     * エクスポーターを初期化
     */
    public function __construct($filename, $bufferSize = 32768, $flushInterval = 1000) {
        $this->fp = fopen($filename, 'w');
        
        if ($this->fp === false) {
            throw new Exception("Failed to open file: {$filename}");
        }
        
        // バッファサイズを設定
        stream_set_write_buffer($this->fp, $bufferSize);
        $this->flushInterval = $flushInterval;
    }
    
    /**
     * CSVヘッダーを書き込み
     */
    public function writeHeader($headers) {
        fputcsv($this->fp, $headers);
    }
    
    /**
     * レコードを書き込み
     */
    public function writeRecord($data) {
        fputcsv($this->fp, $data);
        $this->recordCount++;
        
        // 定期的にフラッシュ
        if ($this->recordCount % $this->flushInterval === 0) {
            fflush($this->fp);
        }
    }
    
    /**
     * 複数レコードを書き込み
     */
    public function writeRecords($records) {
        foreach ($records as $record) {
            $this->writeRecord($record);
        }
    }
    
    /**
     * 統計情報を取得
     */
    public function getStats() {
        return [
            'total_records' => $this->recordCount,
            'file_size' => ftell($this->fp)
        ];
    }
    
    /**
     * クローズ
     */
    public function close() {
        if ($this->fp) {
            fflush($this->fp);  // 最後にフラッシュ
            fclose($this->fp);
            $this->fp = null;
        }
    }
    
    /**
     * デストラクタ
     */
    public function __destruct() {
        $this->close();
    }
}

// 使用例
echo "=== データエクスポート ===\n";

$exporter = new DataExporter('/tmp/export.csv', 32768, 500);

// ヘッダーを書き込み
$exporter->writeHeader(['ID', 'Name', 'Email', 'Age']);

// 大量のデータを書き込み
$startTime = microtime(true);

for ($i = 1; $i <= 10000; $i++) {
    $exporter->writeRecord([
        $i,
        "User {$i}",
        "user{$i}@example.com",
        rand(18, 80)
    ]);
    
    if ($i % 2000 === 0) {
        echo "Exported {$i} records\n";
    }
}

$endTime = microtime(true);
$stats = $exporter->getStats();

echo "\n統計情報:\n";
echo "  総レコード数: {$stats['total_records']}\n";
echo "  ファイルサイズ: " . number_format($stats['file_size']) . " bytes\n";
echo "  処理時間: " . round($endTime - $startTime, 3) . "秒\n";

$exporter->close();

例3: ストリーミングレスポンス

class StreamingResponse {
    private $fp;
    
    /**
     * ストリーミングを開始
     */
    public function __construct() {
        // 標準出力を使用
        $this->fp = fopen('php://output', 'w');
        
        // バッファリングを無効化(即座に出力)
        stream_set_write_buffer($this->fp, 0);
        
        // HTTPヘッダーを設定
        header('Content-Type: text/event-stream');
        header('Cache-Control: no-cache');
        header('Connection: keep-alive');
    }
    
    /**
     * イベントを送信
     */
    public function sendEvent($data, $event = null) {
        if ($event !== null) {
            fwrite($this->fp, "event: {$event}\n");
        }
        
        fwrite($this->fp, "data: " . json_encode($data) . "\n\n");
        
        // 即座にクライアントに送信
        fflush($this->fp);
        
        // PHPの出力バッファもフラッシュ
        if (ob_get_level() > 0) {
            ob_flush();
        }
    }
    
    /**
     * コメントを送信(接続維持用)
     */
    public function sendComment($comment) {
        fwrite($this->fp, ": {$comment}\n\n");
        fflush($this->fp);
    }
    
    /**
     * クローズ
     */
    public function close() {
        if ($this->fp) {
            fclose($this->fp);
            $this->fp = null;
        }
    }
}

// 使用例(実際のHTTPレスポンスで使用)
// echo "=== ストリーミングレスポンス ===\n";
// 
// $stream = new StreamingResponse();
// 
// // 進捗状況を送信
// for ($i = 1; $i <= 10; $i++) {
//     $stream->sendEvent([
//         'progress' => $i * 10,
//         'message' => "Processing step {$i}"
//     ], 'progress');
//     
//     sleep(1);  // 実際の処理をシミュレート
// }
// 
// $stream->sendEvent(['message' => 'Complete!'], 'complete');
// $stream->close();

例4: バッファサイズベンチマーク

class BufferBenchmark {
    /**
     * 異なるバッファサイズでベンチマーク
     */
    public static function benchmark($fileSize, $bufferSizes) {
        $results = [];
        
        foreach ($bufferSizes as $bufferSize) {
            $filename = '/tmp/benchmark_' . $bufferSize . '.txt';
            
            $startTime = microtime(true);
            $startMemory = memory_get_usage();
            
            // ファイルを開いてバッファサイズを設定
            $fp = fopen($filename, 'w');
            stream_set_write_buffer($fp, $bufferSize);
            
            // データを書き込み
            $written = 0;
            $chunkSize = 1024;  // 1KB チャンク
            
            while ($written < $fileSize) {
                $data = str_repeat('x', min($chunkSize, $fileSize - $written));
                fwrite($fp, $data);
                $written += strlen($data);
            }
            
            fclose($fp);
            
            $endTime = microtime(true);
            $endMemory = memory_get_usage();
            
            $results[$bufferSize] = [
                'time' => $endTime - $startTime,
                'memory' => $endMemory - $startMemory,
                'file_size' => filesize($filename)
            ];
            
            // クリーンアップ
            unlink($filename);
        }
        
        return $results;
    }
    
    /**
     * 結果を表示
     */
    public static function displayResults($results) {
        echo "バッファサイズ\t時間(秒)\tメモリ(bytes)\n";
        echo str_repeat('-', 50) . "\n";
        
        foreach ($results as $bufferSize => $result) {
            $bufferLabel = $bufferSize === 0 ? 'unbuffered' : 
                          ($bufferSize >= 1024 ? ($bufferSize / 1024) . 'KB' : $bufferSize . 'B');
            
            echo sprintf(
                "%s\t\t%.4f\t\t%d\n",
                $bufferLabel,
                $result['time'],
                $result['memory']
            );
        }
    }
}

// 使用例
echo "=== バッファサイズベンチマーク ===\n";

// 1MBのファイルで異なるバッファサイズをテスト
$fileSize = 1024 * 1024;  // 1MB
$bufferSizes = [
    0,      // unbuffered
    1024,   // 1KB
    4096,   // 4KB
    8192,   // 8KB
    16384,  // 16KB
    32768,  // 32KB
    65536   // 64KB
];

echo "ファイルサイズ: " . ($fileSize / 1024) . "KB\n\n";

$results = BufferBenchmark::benchmark($fileSize, $bufferSizes);
BufferBenchmark::displayResults($results);

// 最速のバッファサイズを見つける
$fastest = array_reduce(array_keys($results), function($carry, $key) use ($results) {
    return $carry === null || $results[$key]['time'] < $results[$carry]['time'] ? $key : $carry;
});

echo "\n最速のバッファサイズ: " . ($fastest / 1024) . "KB\n";

例5: プログレスバー付きファイルコピー

class BufferedFileCopier {
    /**
     * バッファリングを使用してファイルをコピー
     */
    public static function copy($source, $destination, $bufferSize = 8192, $callback = null) {
        if (!file_exists($source)) {
            throw new Exception("Source file does not exist: {$source}");
        }
        
        $sourceSize = filesize($source);
        $fpSource = fopen($source, 'r');
        $fpDest = fopen($destination, 'w');
        
        if ($fpSource === false || $fpDest === false) {
            throw new Exception("Failed to open files");
        }
        
        // 書き込みバッファを設定
        stream_set_write_buffer($fpDest, $bufferSize);
        
        $totalRead = 0;
        
        while (!feof($fpSource)) {
            $data = fread($fpSource, $bufferSize);
            fwrite($fpDest, $data);
            
            $totalRead += strlen($data);
            
            // コールバックで進捗を通知
            if ($callback !== null) {
                $progress = $sourceSize > 0 ? ($totalRead / $sourceSize) * 100 : 100;
                call_user_func($callback, $totalRead, $sourceSize, $progress);
            }
        }
        
        fclose($fpSource);
        fclose($fpDest);
        
        return [
            'success' => true,
            'bytes_copied' => $totalRead,
            'source_size' => $sourceSize
        ];
    }
    
    /**
     * プログレスバーを表示
     */
    public static function showProgress($current, $total, $percentage) {
        $barLength = 50;
        $filled = (int)($barLength * ($percentage / 100));
        $bar = str_repeat('=', $filled) . str_repeat('-', $barLength - $filled);
        
        echo sprintf(
            "\r[%s] %d%% (%s / %s)",
            $bar,
            (int)$percentage,
            self::formatBytes($current),
            self::formatBytes($total)
        );
        
        if ($percentage >= 100) {
            echo "\n";
        }
    }
    
    /**
     * バイトを人間が読みやすい形式に変換
     */
    private static function formatBytes($bytes) {
        $units = ['B', 'KB', 'MB', 'GB'];
        $i = 0;
        
        while ($bytes >= 1024 && $i < count($units) - 1) {
            $bytes /= 1024;
            $i++;
        }
        
        return round($bytes, 2) . ' ' . $units[$i];
    }
}

// 使用例
echo "=== バッファリング付きファイルコピー ===\n";

// テスト用の大きなファイルを作成
$sourceFile = '/tmp/large_source.txt';
$destFile = '/tmp/large_dest.txt';

echo "テストファイルを作成中...\n";
$fp = fopen($sourceFile, 'w');
for ($i = 0; $i < 10000; $i++) {
    fwrite($fp, str_repeat('x', 1024) . "\n");  // 1KB行を10000回
}
fclose($fp);

echo "ファイルをコピー中...\n";

$result = BufferedFileCopier::copy(
    $sourceFile,
    $destFile,
    32768,  // 32KB バッファ
    ['BufferedFileCopier', 'showProgress']
);

echo "\nコピー完了\n";
echo "コピーされたバイト数: " . number_format($result['bytes_copied']) . "\n";

// クリーンアップ
unlink($sourceFile);
unlink($destFile);

例6: リアルタイムログモニター

class RealtimeLogMonitor {
    private $fp;
    private $filename;
    
    /**
     * モニターを初期化
     */
    public function __construct($filename) {
        $this->filename = $filename;
        $this->fp = fopen($filename, 'a');
        
        if ($this->fp === false) {
            throw new Exception("Failed to open log file");
        }
        
        // 無バッファリングで即座に書き込み
        stream_set_write_buffer($this->fp, 0);
    }
    
    /**
     * ログエントリを追加
     */
    public function log($level, $message) {
        $timestamp = microtime(true);
        $formatted = sprintf(
            "[%.6f] [%s] %s\n",
            $timestamp,
            $level,
            $message
        );
        
        fwrite($this->fp, $formatted);
        
        return $timestamp;
    }
    
    /**
     * タイミング付きログ
     */
    public function logWithTiming($level, $message, $startTime = null) {
        $currentTime = microtime(true);
        
        if ($startTime !== null) {
            $elapsed = ($currentTime - $startTime) * 1000;  // ミリ秒
            $message .= sprintf(" [took: %.2fms]", $elapsed);
        }
        
        return $this->log($level, $message);
    }
    
    /**
     * バッチ処理のログ
     */
    public function logBatch($level, $messages) {
        foreach ($messages as $message) {
            $this->log($level, $message);
        }
    }
    
    /**
     * クローズ
     */
    public function close() {
        if ($this->fp) {
            fclose($this->fp);
            $this->fp = null;
        }
    }
    
    /**
     * デストラクタ
     */
    public function __destruct() {
        $this->close();
    }
}

// 使用例
echo "=== リアルタイムログモニター ===\n";

$monitor = new RealtimeLogMonitor('/tmp/realtime.log');

// 処理をログ
$startTime = $monitor->log('INFO', 'Starting process');

sleep(0.1);
$monitor->logWithTiming('INFO', 'Step 1 completed', $startTime);

sleep(0.2);
$monitor->logWithTiming('INFO', 'Step 2 completed', $startTime);

sleep(0.05);
$monitor->logWithTiming('INFO', 'Step 3 completed', $startTime);

// バッチログ
$monitor->logBatch('DEBUG', [
    'Debug message 1',
    'Debug message 2',
    'Debug message 3'
]);

$monitor->logWithTiming('INFO', 'Process finished', $startTime);
$monitor->close();

echo "ログファイル: /tmp/realtime.log\n";
echo "\nログ内容:\n";
echo file_get_contents('/tmp/realtime.log');

パフォーマンスへの影響

// バッファサイズとパフォーマンスの関係

// 小さいバッファ(頻繁なディスクアクセス)
// - メモリ使用量: 少ない
// - ディスクI/O: 多い
// - 速度: 遅い
stream_set_write_buffer($fp, 1024);  // 1KB

// 中程度のバッファ(バランス型)
// - メモリ使用量: 中程度
// - ディスクI/O: 中程度
// - 速度: 中程度
stream_set_write_buffer($fp, 8192);  // 8KB(デフォルト)

// 大きいバッファ(メモリトレードオフ)
// - メモリ使用量: 多い
// - ディスクI/O: 少ない
// - 速度: 速い
stream_set_write_buffer($fp, 65536);  // 64KB

// 無バッファリング(リアルタイム性重視)
// - メモリ使用量: 最小
// - ディスクI/O: 最大
// - 速度: 最遅(但しデータロスのリスクが最小)
stream_set_write_buffer($fp, 0);

使い分けガイド

// 使用場面に応じたバッファサイズ

// 1. クリティカルなログ(システムエラー、セキュリティイベント)
// → 無バッファリング(データロス防止)
stream_set_write_buffer($fp, 0);

// 2. 通常のアプリケーションログ
// → デフォルト(8KB)
stream_set_write_buffer($fp, 8192);

// 3. 大量のデータエクスポート
// → 大きいバッファ(32-64KB)
stream_set_write_buffer($fp, 32768);

// 4. リアルタイムストリーミング
// → 無バッファリング
stream_set_write_buffer($fp, 0);

// 5. バッチ処理
// → 大きいバッファ
stream_set_write_buffer($fp, 65536);

まとめ

set_file_buffer()stream_set_write_buffer())関数の特徴をまとめると:

できること:

  • ファイル書き込みバッファのサイズ設定
  • バッファリング動作の制御
  • パフォーマンスの最適化

推奨事項:

  • PHP 8.0以降はstream_set_write_buffer()を使用
  • set_file_buffer()はエイリアス(互換性のため残存)

バッファサイズの選択:

  • 0: 無バッファリング(即座に書き込み)
  • 40968192: 通常のログやファイル
  • 3276865536: 大量データの書き込み

推奨される使用場面:

  • ログファイル管理
  • データエクスポート
  • ストリーミング出力
  • リアルタイム処理
  • バッチ処理

注意点:

  • バッファが満杯になるまで実際の書き込みは発生しない
  • fflush()で強制的にフラッシュ可能
  • プロセス終了時やfclose()で自動フラッシュ
  • 無バッファリングはパフォーマンスが低下

関連関数:

  • fflush(): バッファを強制フラッシュ
  • fwrite(): ファイルに書き込み
  • fclose(): ファイルを閉じる(自動フラッシュ)
  • stream_get_meta_data(): ストリーム情報取得

よく使うパターン:

// 重要なログ(無バッファリング)
$fp = fopen('critical.log', 'a');
stream_set_write_buffer($fp, 0);
fwrite($fp, $logEntry);

// 通常のファイル(デフォルト)
$fp = fopen('data.txt', 'w');
stream_set_write_buffer($fp, 8192);
fwrite($fp, $data);

// 大量データ(大きいバッファ)
$fp = fopen('export.csv', 'w');
stream_set_write_buffer($fp, 65536);
foreach ($records as $record) {
    fputcsv($fp, $record);
}

set_file_buffer()stream_set_write_buffer())は、ファイル書き込みのパフォーマンスを制御する重要な関数です。適切なバッファサイズを選択することで、アプリケーションの性能を最適化できます!

タイトルとURLをコピーしました