[flock] バッファ操作関連のまとめ†
- ページ: BugTrack2
- 投稿者: henoheno
- 優先順位: 普通
- 状態: 着手
- カテゴリー: 本体バグ
- 投稿日: 2006-03-20 (月) 00:02:44
- バージョン:
メッセージ†
flock()周辺のバッファ操作(バッファのon/off、フラッシュ等)について、本来どのようにするべきであるかをシチュエーション別に明確にしましょう。
参考文献: set_file_buffer()†
PukiWiki 1.4.x は少なくともPHP4.1.2以降ターゲットにしているため、stream_set_write_buffer() ではなく set_file_buffer() を使用しています。
- STACK*:PHP中級 http: //www.stackasterisk.jp/tech/php/php02_01.jsp
- (PHP開発版のトピック) [PHP-users 14972]PHP 4.3.2RC2のstream_set_write_buffer(set_file_buffer)が失敗する
参考文献: fflush()†
- ソース: ext/standard/file.c
- PHPマニュアル: fflush()
- 一回の実行の間に「ファイルに書き込んでそれを再び読む」シチュエーションがある時、clearstatcache()を使用しないと、PHPのキャッシュによって実行時のファイル内容を読んでしまう旨コメント(by draco at lomag dot net)あり。
ステータスキャッシュ関連†
ファイル操作上のステータスキャッシュの問題 (このページと直接は関係ない)
- PHPマニュアル: fflush()
- 一回の実行の間に「ファイルに書き込んでそれを再び読む」シチュエーションがある時、clearstatcache()を使用しないと、PHPのキャッシュによって実行時のファイル内容を読んでしまう旨コメント(by draco at lomag dot net)あり。
- 再現コード (by someone)
$filename = 'testdata.txt';
$fp = fopen($filename, 'w+'); // ファイルサイズは強制的に0になる
//set_file_buffer($fp, 0);
echo filesize($filename)."\n"; // 0バイトであるはず
fputs($fp, str_repeat('a', 10)); // 10バイト書き込み
fflush($fp);
fclose($fp);
echo filesize($filename)."\n"; // 10バイトであるはず
clearstatcache();
echo filesize($filename)."\n"; // 10バイトであるはず
- fflush使わずset_file_bufferでも同じ結果にならないですか? --
- 再現しない(同じ数値が表示される)けど、PHP本体のバージョンや設定が影響したりする? --
- 全部10ならclearstatcacheする必要はないのかな --
- 一回目のfilesize取得が間違っていたので直しました。0,0,10が返ってくると思います。 --
- filesize()を使わないでfeofなどファイルポインタを使えば問題は出ません(file()などの関数はこちらをつかっている)。また、他のファイルのstat取得でもclearstatcache()と同等の働きがあります(直前の1ファイルしかcacheされないため)。 --
- そのサンプルコード、あっても無くても関係のないset_file_buffer() やfflush()がそこに書いてあるのは何故ですか -- henoheno
- fflush()やset_file_buffer()の問題ではなくステートキャッシュの問題なのを示すためです。多分元のdraco at lomag dot netのコメントは、read modify writeな局面でfread()にfilesize()を引数として使った結果失敗したのでしょうけれど、端的に示すには細かいコードを書くより理解し易いでしょう。 --
(BugTrack2/151より移動)
コメント: バッファ操作?†
- cvs:lib/file.php(1.51)の変更ではファイル書き込みの短縮に主眼があるようですが、バッファリング無しにして数千行書き込めば遅くなるのも当然ではありませんか?
それから「put_lastmodified() の負荷軽減」なのですからrecent.datの処理だけでなく何故このタイミングでautolink.datの更新処理までしなければならないのかという点も検討した方がいいと思いますよ。 --
- こんにちは :) コードの機能分割については放っておいてもそうなるので大丈夫です。バッファ操作について確認させて下さい。set_file_buffer() を取れと言われていますか、それともさらに細かく追加しろと言われていますか?なおバッファを一旦切ることについては、理解している範囲では必須であるはずです。 -- henoheno
- コードの機能分割云々は意味がよく分からないのでバッファ操作についてですが、flock処理しているところでは解放前に最低限OS側へデータを渡さねばならないですからバッファリング無しかバッファをフラッシュするかの2択しかないわけですよね。で、このコード周りはバッファ無しの方法を選択してループして一行ずつ出力しているという事なのでしょうが、ファイル出力なので塊にして出力した方がOS側がダーティーデータの管理を省けるのでスループットが上がるという単純な話です*1。メモリが勿体無いのであればバッファリング有りで解放前にフラッシュするほうを選択すれば良いだけです。ですがこの場合recent.datのファイルサイズは多くて1M程度と見積もっているのでこの場所でデータ連結用の一時変数を使っても構わないと踏んでます。こんなところで良いですか? --
- いえ。そのバッファ操作(切る場合、フラッシュする場合ですか)のコードをPHPで具体的にどうすべきとお考えなのかを提示していただけますか。 set_file_buffer() に関するコメントもお願いします。recent.datは件数ベースであれば仰る通り量は少ないと思います。メモリの確保と廃棄はflock()を取った後の方が良さそうですね(複数セット確保されるのは勿体無いので)。 -- henoheno
- 具体的なコードは下のものですよ。set_file_bufferへのコメントとは取るか取らないかでしたらこのケースではサイズからいってどちらでもいいと思いますよ。最後のflockを取った後でというのはよくわかりません。 --
- こんにちは。これで、最初のコメントで、どのように思われて「バッファリング無しに・・・」とコメントされていたのかがやっと判りました :) set_file_buffer() の意味を何か誤解されていませんか。(私が誤解しているなら指摘して下さい) -- henoheno
- set_file_buffer() は万一のファイルの破壊を防ぐべく、出力バッファをクリアするために挿入された物のはずなので、破壊が起きるシチュエーションが絶対にないなら絶対にいらないし、可能性があるなら絶対に必要です。 -- henoheno
- 私がここで明確にさせていただきたかったのは、「その提案は本当にバッファをコントロールしている事になるのか」と「バッファをさらにコントロールするために何か努力ができるのか、それともできないのか」です。例えば: -- henoheno
- (1) flock() の直後で set_file_buffer() を使ってバッファを再設定すべきだ。そうすれば問題ない上にさらに早くなる。検証してみたが(以下略)
- (2) このシチュエーションでは set_file_buffer() は絶対に不要だ。その理由は(以下略)
- (3) このシチュエーションなら、fflush() をこう使って(以下略)
- 取り扱うデータの量の大小でデータの破壊が左右されるはずがありませんから、それは set_file_buffer() を外す理由にならないと思います。 -- henoheno
- 確認しておいたほうがいいようですので、まず最初に下のコードの意図ですが、fputs()のコール回数を減らす事ではなくset_file_buffer()で停止したバッファリング動作を肩代わりする事に有ります。バッファ動作をコントロール下に置くと言い換える事もできるでしょうが、その場合の選択肢は12日に書いた2種類になります。続いて「万一のファイル破壊を防ぐ」の意味ですが、ここでのファイル破壊は排他処理が完全でなく別プロセスの処理によって引き起こされるものです。決してサーバー側の事故(停電・ディスククラッシュ等)によって起こされる破壊に対するものではありません。 --
- それから、set_file_buffer()はバッファリングを切るために呼ぶのであってバッファサイズの変更などで呼ぶ必要はないでしょう。切らないのであればロック解放前にfflush()を呼ぶ必要があるだけです(何度も書いていますが)。文章だとどこまで理解してもらえているか判然としないので説明が難しいですねぇ…。 --
(BugTrack2/151)
コメント: fputs()のコール回数を減らす案†
- ファイル書き込みを短縮するだけならこれを試してください(何も削らないが速度は改善するはず)。 --
--- file.1.51.php Fri Mar 10 21:45:44 2006
+++ file.1.51.test.php Fri Mar 10 21:51:22 2006
@@ -303,7 +303,10 @@
arsort($recent_pages, SORT_NUMERIC);
// Cut unused lines
- $recent_pages = array_splice($recent_pages, 0, $maxshow);
+// $recent_pages = array_splice($recent_pages, 0, $maxshow);
+ $data = '';
+ foreach ($recent_pages as $page=>$time)
+ $data .= $time . "\t" . $page . "\n";
// Create recent.dat (for recent.inc.php)
$file = 'recent.dat';
@@ -315,10 +318,12 @@
set_file_buffer($fp, 0);
flock($fp, LOCK_EX);
rewind($fp);
- foreach ($recent_pages as $page=>$time)
- fputs($fp, $time . "\t" . $page . "\n");
+// foreach ($recent_pages as $page=>$time)
+// fputs($fp, $time . "\t" . $page . "\n");
+ fputs($fp, $data);
flock($fp, LOCK_UN);
fclose($fp);
+ unset($data);
// Create RecentChanges
$fp = fopen(get_filename($whatsnew), 'w') or
- コメントありがとうございます。fputs() のコール回数を減らす(CPUのオーバーへッド削減)という後半の部分が主題のようですね。ただ、そのために前半で配列を確保することになっています(メモリオーバーヘッド増加)。この一連の重たい処理(編集処理)の中で、さらにメモリを要求する事は避けたいところです。ただTipsとしてはありますよね :) -- henoheno
(STACK*の) set_file_buffer($fp, 0) の説明に疑問...†
長文で申し訳ありません. 引用されている set_file_buffer($fp,0) の説明に
疑問を感じたため、手元で確認した結果をこちら記載します.
検証方法の不備や、さらに提供すべき設定内容があれば、お知らせください - jjyun
上には set_file_buffer() の説明として以下のような記述が掲載されていますが...
STACK*:PHP中級
- 説明の仕方としては以下引用の通り
バッファがあると、せっかく flock を使ってロックしても、書き込まれる前の
データをディスクから読み出してしまうことがあるわけです
PHP のマニュアルによると、set_file_buffer($fp,0) は stream_set_write_buffer($fp,0)
であり、『書き込み操作はバッファされなくなります』 とあります. でも 『読み込み操作がブロックされる』とは書いてありません.
ちなみに、PHP5.1.2 の stream_set_write_buffer() のコードからバッファを設定している部分を
抜粋すると以下のようになっています.
// if buff is 0 then set to non-buffered
if( buf == 0 ) {
ret = php_stream_set_option(stream, PHP_STREAM_OPTION_WRITE_BUFFER, PHP_STREAM_BUFFER_NONE, NULL);
} else {
ret = php_stream_set_option(stream, PHP_STREAM_OPTION_WRITE_BUFFER, PHP_STREAM_BUFFER_FULL, &buff);
}
これを検証するために以下のようなコードを準備してみました.
- 書き込み(書き込みを数回行うケース): writer1.php
- 書き込み(書き込みを1回で行うケース): writer2.php
- 読み込み(writer1.php あるいは writer2.php で書き出されたファイルを読み込む): reader.php
以下の結果を確認することで、『set_file_buffer() が 読み込み処理をブロックするか』ということを
確認してみたいと思います.
- writer1.php と reader.php を同時に起動させる. : set_file_buffer_test1.pl
- writer2.php と reader.php を同時に起動させる. : set_file_buffer_test2.pl
テスト環境/テストデータ は以下の通り.
- OS: FedoraCore4
- PHP: PHP 5.0.4 (cli) (built: Nov 8 2005 08:27:12)
- writer1.php writer2.php の元データ (rdata) : 適当に作成した約2MBのアスキーデータ.
writer1.php†
<?php
print("writer1.start\n");
$rfile = "rdata";
$rlines = file($rfile)
or die_message("$rfile isnot exist");
$wfile = "wdata";
$fp = @fopen( $wfile, file_exists($wfile) ? "r+": "w")
or die_message("$wfile cannot create.");
set_file_buffer($fp, 0);
foreach( $rlines as $line_num => $line)
{
print("check\n");
fwrite($fp,$line);
}
fclose($fp);
print("writer1.end\n");
?>
writer2.php†
<?php
print("writer2.start\n");
$rfile = "rdata";
$contents = file_get_contents($rfile)
or die_message("$rfile not exist");
$wfile = "wdata";
$fp = @fopen( $wfile, file_exists($wfile) ? "r+": "w")
or die_message("$file cannot create.");
set_file_buffer($fp, 0);
fwrite($fp, $contents);
print("writer2.write.finish\n");
fclose($fp);
print("writer2.end\n");
?>
reader.php†
<?php
print("reader.start\n");
$rfile = "wdata";
while( ! $contents = @file_get_contents($rfile) )
;
print("reader: read finish.\n");
$wfile = "wdata2";
$fp = @fopen( $wfile, file_exists($wfile) ? "r+": "w")
or die_message("$wfile not exist");
print("reader.output.start\n");
fwrite($fp,$contents);
print("reader.output.end\n");
fclose($fp);
print("reader.end\n");
?>
set_file_buffer_test1.pl とその結果†
#!//usr/bin/perl
unlink("wdata");
unlink("wdata2");
system("php -q writer1.php &");
# busy loop
for($i=0;$i<6000;$i++){
;
}
system("php -q reader.php &");
exit;
以下のように書き込み処理の後に読み込み処理が行われることもあれば、
writer1.start
check
check
:
check
writer1.end
reader.start
reader: read finish.
reader.output.start
reader.output.end
reader.end
読み込み処理が書き込み処理中に行われることもあります.
reader.start
writer1.start
check
reader: read finish.
check
reader.output.start
reader.output.end
reader.end
check
check
:
check
check
writer1.end
set_file_buffer_test2.pl とその結果†
#!//usr/bin/perl
unlink("wdata");
unlink("wdata2");
system("php -q writer2.php &");
# busy loop;
for($i=0;$i<6000;$i++) {
;
}
system("php -q reader.php &");
exit;
こちらも、以下のように書き込み処理の後に読み込み処理が行われることもあれば、
writer2.start
writer2.write.finish
writer2.end
reader.start
reader: read finish.
reader.output.start
reader.output.end
reader.end
writer2.start
reader.start
writer2.write.finish
writer2.end
reader: read finish.
reader.output.start
reader.output.end
reader.end
こちらも、以下のように書き込み処理の後に、読み込み処理が先に終わってしまうこともあります.
writer2.start
reader.start
reader: read finish.
reader.output.start
reader.output.end
reader.end
writer2.write.finish
writer2.end
最後の場合だけ申し訳ありませんが、この時のファイルサイズは以下のような状態でした.
- writer2 の読み込みデータ rdata のサイズ : 2142150
- writer2 が書き出した wdata のサイズ : 2142150
- reader が書き出した wdata2 のサイズ : 8192
結論.†
『 set_file_buffer() は 書き込みバッファに対する処理であって、
読み込み処理をブロックすることはない. 』
と考えます.
- 中途半端な書き込み中のデータを読み込まないようにするには、write処理と排他できるように flock() することが必要だと思います. -- jjyun
- でも、PHPマニュアルには『flock()はNFS及び他の多くのネットワークファイ ルシステムでは動作しません。』 とあります. このようなシステム上も考慮すべきだと思うのですがいかがでしょうか?
flock() を ディレクトリの作成を用いたロジックで置き換えるという記事を見かけたこともありましたが、処理のタイムアウトが起きた場合を憂慮しています. タイムアウト発生時に、作成したlock用のディレクトリの削除を行わせることはできるでしょうか? -- jjyun
phpのflockはfcntl()が使用可能かコンパイル時に調べて使えるなら優先的に使うようですので、ほとんどのNFSで問題は起きないと思います。使うコードは入っていますが優先的ではないようです。確証がないのに書いて大変申し訳ありません。 --
- 結論で言われている事(読み込む側がブロックするわけじゃない)はその通りじゃないかと思うのですが、発端の部分が妙だと思います。 -- henoheno
- この件(このjjyunさんのトピック)であれば、そのケチをつけた STACK* の文言についてまさに検証しなければならないでしょう。「書き込まれる前のデータをディスクから読み出してしまう」という記述が本当に起こりうるのか、それはどんな時に起こるかを検討し、まさにそれを狙ったコードでなくてはいけません。 -- henoheno
- PHPの実装を少しばかり調べてみたので、一時的にここでまとめてみました。『set_file_buffer() を外す理由』にもなると思います。 -- ko-zu
- 私がSTACK*の記事やPHPマニュアルの行間を読み違えたために、皆様を混乱させて申し訳ありませんでした. 現在、私の認識は ko-zu さんが纏めていただいた内容とほぼ同じです. ko-zuさんありがとうございます. -- jjyun
- ko-zu さんの説明と私の認識と違うところ: STACK*の カウンタプログラムは、r+ で開いた場合、rewind($fp)で操作を行う位置は変更していても、ftruncate($fp,0) をしていないので、(サイズゼロのファイルではなく ...やはり) 『書き込まれる前のデータをディスクから読み出してしまう』 ことになりませんか? -- jjyun
- ftruncate(が無いこと)を見落としていました。修正加筆しました。 -- ko-zu
- ko-zuさんとこの最後にある STACK* 向けの検証コードは、ファイルバッファを確実に超えてプロセスがブロックする様なデータを fwrite() している事を保障していない(例えばファイルバッファの値をソースで調べてそれを超えるデータを作っているわけでないし、set_file_buffer(1)などをしているわけでもない)し、flock()を使っているわけでもないし、一度完全に書き込み終わったデータを読み取ってしまう可能性について言及していないし、それを実証するようなコードではない(例えば毎回異なる値を書き込むようにはなっていない。そうしなければ、以前のデータを読んだかが実証できない)ので、実証するための条件をまだ満たしていないんじゃないでしょうか。 -- henoheno
- flockの影響を検証するかのような文面になってしまっていたので修正しました。 -- ko-zu
- お疲れ様です。この話題(STACK* に書かれている事の検証)は終わったようには見えていなくて、set_file_buffer() が php_stream の設定を変更する役割をしている、という部分より先の事は示せていない様に見えています。set_file_buffer(0)をする前の状態がどんな挙動をするかを踏まえて、掘り下げる事ができたらぜひコメントして下さい。(書き込む際にブロックする部分の実装がどうなっているのかは私も追えていないので) -- henoheno
- 久々にPHPソースを読む時間が出来たのでドキュメント再考とストリームに少々まとめてみました。 -- ko-zu
- 個人的な意見としては、set_file_buffersは巨大なファイルを扱う際にパフォーマンス上のトリックとして利用できますが、やはりロックには関係せず、fcloseで十分であるという結論を得ました。また、PHPのflockは常に内部的にfflushをコールするようなので、記事のコード例でも、set_file_buffersとfflushを呼ぶ必要はなさそうです。
ところで (引用が一部無くなっている?)†
- ところで『set_file_buffer()』と『ブロック』を結びつける元になった記述はどこにいったのでしょう? --
- 「ブロック」という単語でこのjjyunさんのトピックを検索されるか、もう少し
えぐるような具体的に踏み込んだコメントを下さい。 -- henoheno
- このページのpreの部分にSTACKの記事から引用した書きこみデータをブロックする旨の記述があったはずなんですがそれがどこにいったのでしょうと問うています。元の記事はいくらでも書きかえられるでしょうけれど、ここに無くなっているのが不審なのですよ。 --
- ここはヒアリング大会の会場ではないので、可能な限り明確なコメントを最初からして下さい。 -- henoheno
- で、どこにいったのですか?バックアップにも無いですが? --
- set_file_buffer() と ブロックを結び付けて考えるきっかけになった記事は 『 バッファがあると、せっかく flock を使ってロックしても、書き込まれる前のデータをディスクから読み出してしまうことがあるわけです』 の部分です...
(恥の上塗りですが)私が誤解した経緯は、pukiwikiのflock() の返り値が検査されていない理由がset_file_buffer() にあるのだろうか...という考えを持って、set_file_buffer() 関連のマニュアルの読み、あたかも set_file_buffer() によって読み込み処理がブロックされるのかと誤解したのです..orz. -- jjyun
- あいまいな方>まだ踏み込みが浅い様ですね。set_file_buffer() が読み込み側のプロセスをブロックするんじゃないか、という誤解が上のトピックあるんじゃないか、といった部分は今の状態でも十分に表現されているはずで、誰か(または集団)の推敲の途中で引用部分が足されたり削られたりしたのかもしれないけれど、その有無が本筋に影響を与えるとは思えません。どう思ったのか、もっと自問自答を繰り返してそれを最初にズバっと書いていただけませんか。そうであれば私もこうした応答を書く事ができ、すばやく本来の話題に戻れるでしょう。Wikiのコンテンツを推敲して行く事にお慣れでなくて、バックアップ関係のPukiWikiの挙動に詳しくないという事も伝わりました。それもご自身で確認してください。 -- henoheno
コメント: PHP 5.3.2以降のfclose()†
- PHP Manual のflock関数より
変更履歴
バージョン | 説明 |
5.3.2 | ファイルのリソースハンドルを閉じたときにロックを自動的に解放する機能が削除されました。 ロックの解放は、常に手動で行わなければなりません。 |
だそうです。「fclose 前にアンロックしている」は、むしろ存在していないと困る*3という・・・ --
- こんにちは。PHPのソースを見ないでコメントしますが、PHP fclose() の中で C言語API*4の fclose() が依然として呼ばれているのだとしたら、依然としてロックは後者のfclose()により開放されると思います*5。そのマニュアルのコメント欄でも webmaster at bitpush dot com さんがそれっぽい事を書いています。そのため、「ファイルのリソースハンドルを閉じたときにロックを自動的に解放する機能が削除されました。」という下りは、本来不要な処理を取っただけとも受け取れるのですが、後半の意味が不明です。明確に書けよよという事なのでしょうか。どうなんでしょう -- henoheno
- Version 5.3.2 "Removed automatic file descriptor unlocking happening on shutdown and/or stream close
(on all OSes excluding Windows)(on all OSes). (Tony, Ilia)" http: //www.php.net/ChangeLog-5.php#5.3.2
- 提示されたPHPのChangeLogですが、こちらに転載された文は5.2.13のものらしく、5.3.2のところでは(on all OSes) と対象が全OSになっておりました。 -- 2011-01-19 (水) 18:09:47のコメント主
- こちらは、マニュアルのコメント欄よりも前までだけしか読んでませんでした。前半ということは、今のままアンロックのコードが存在(明示的に開放)してないと~というつもりで書いてましたが、提示された情報からするとこちらの勘違いのようですね。申し訳ありません -- 2011-01-19 (水) 18:09:47のコメント主
- 後から追えるようにこの件のSVNをメモしておきます。http: //svn.php.net/viewvc?view=revision&revision=292632 -- 2011-01-19 (水) 18:09:47のコメント主
- PHPAPI int php_flock http: //svn.php.net/viewvc/php/php-src/branches/PHP_5_3/ext/standard/flock_compat.c --
- PHP_FUNCTION(flock), PHP_FUNCTION(fclose) http: //svn.php.net/viewvc/php/php-src/branches/PHP_5_3/ext/standard/file.c --
- この件は、ここまでの推測(本来不要な処理を取っただけ)が正しいのであれば、コミットログおよびChangelogに理由を明記していないのが問題です。 -- henoheno