こんにちは!今回は、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: 無バッファリング(即座に書き込み)4096–8192: 通常のログやファイル32768–65536: 大量データの書き込み
推奨される使用場面:
- ログファイル管理
- データエクスポート
- ストリーミング出力
- リアルタイム処理
- バッチ処理
注意点:
- バッファが満杯になるまで実際の書き込みは発生しない
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())は、ファイル書き込みのパフォーマンスを制御する重要な関数です。適切なバッファサイズを選択することで、アプリケーションの性能を最適化できます!
