* [flock] バッファ操作関連のまとめ [#rcf7f135]

- ページ: [[BugTrack2]]
- 投稿者: [[henoheno]]
- 優先順位: 普通
- 状態: 提案
- カテゴリー: 本体バグ
- 投稿日: 2006-03-20 (月) 00:02:44
- バージョン: 

#contents

** メッセージ [#j4ad2ce5]

flock()周辺のバッファ操作(バッファのon/off、フラッシュ等)について、本来どのようにするべきであるかをシチュエーション別に明確にしましょう。

*** 参考文献: set_file_buffer() [#j8545212]
- ソース: ext/standard/file.c

PukiWiki 1.4.x は少なくともPHP4.1.2以降ターゲットにしているため、stream_set_write_buffer() ではなく set_file_buffer() を使用しています。

- PHPマニュアル:  [[set_file_buffer()>PHPfunc:set_file_buffer]]
- PHPマニュアル:  [[stream_set_write_buffer()>PHPfunc:stream_set_write_buffer]]
-- バッファのサイズを0にする事で、書き込みの最中にブロックしない事を保証する


- [[STACK*:PHP中級>http://www.stackasterisk.jp/tech/php/php02_01.jsp]]
-- 説明の仕方としては以下引用の通り
  バッファがあると、せっかく flock を使ってロックしても、書き込まれる前の
  データをディスクから読み出してしまうことがあるわけです

- (PHP開発版のトピック) [PHP-users 14972]PHP 4.3.2RC2のstream_set_write_buffer(set_file_buffer)が失敗する
-- http://ns1.php.gr.jp/pipermail/php-users/2003-April/015504.html
-- http://ns1.php.gr.jp/pipermail/php-users/2003-May/015516.html
 PHP 4.3.2RC*ではローカルファイルへのユーザレベルIOバッファの機能は
 *無い*ので、set_file_bufferはするだけ無駄
-- Bug #23201 http://bugs.php.net/bug.php?id=23201 (Fixed at PHP 4.3.2)

*** 参考文献: fflush() [#e5387b9f]
- ソース: ext/standard/file.c
- PHPマニュアル:  [[fflush()>PHPfunc:fflush]]
-- 一回の実行の間に「ファイルに書き込んでそれを再び読む」シチュエーションがある時、[[clearstatcache()>PHPfunc:clearstatcache]]を使用しないと、PHPのキャッシュによって実行時のファイル内容を読んでしまう旨コメント(by draco at lomag dot net)あり。


** ステータスキャッシュ関連 [#a73af28d]
ファイル操作上のステータスキャッシュの問題 (このページと直接は関係ない)

- PHPマニュアル:  [[fflush()>PHPfunc:fflush]]
-- 一回の実行の間に「ファイルに書き込んでそれを再び読む」シチュエーションがある時、[[clearstatcache()>PHPfunc: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でも同じ結果にならないですか? --  &new{2006-03-21 (火) 01:22:41};
- 再現しない(同じ数値が表示される)けど、PHP本体のバージョンや設定が影響したりする? --  &new{2006-03-21 (火) 12:36:15};
- 全部10ならclearstatcacheする必要はないのかな --  &new{2006-03-21 (火) 19:47:48};
- 一回目のfilesize取得が間違っていたので直しました。0,0,10が返ってくると思います。 --  &new{2006-03-21 (火) 23:11:08};
-- filesize()を使わないでfeofなどファイルポインタを使えば問題は出ません(file()などの関数はこちらをつかっている)。また、他のファイルのstat取得でもclearstatcache()と同等の働きがあります(直前の1ファイルしかcacheされないため)。 --  &new{2006-03-21 (火) 23:27:44};
- そのサンプルコード、あっても無くても関係のないset_file_buffer() やfflush()がそこに書いてあるのは何故ですか -- [[henoheno]] &new{2006-03-21 (火) 23:51:35};
-- fflush()やset_file_buffer()の問題ではなくステートキャッシュの問題なのを示すためです。多分元のdraco at lomag dot netのコメントは、read modify writeな局面でfread()にfilesize()を引数として使った結果失敗したのでしょうけれど、端的に示すには細かいコードを書くより理解し易いでしょう。 --  &new{2006-03-22 (水) 06:32:22};

#comment

--------
([[BugTrack2/151]]より移動)

** コメント: バッファ操作? [#bdd30207]
- [[cvs:lib/file.php]](1.51)の変更ではファイル書き込みの短縮に主眼があるようですが、バッファリング無しにして数千行書き込めば遅くなるのも当然ではありませんか?&br;それから「put_lastmodified() の負荷軽減」なのですからrecent.datの処理だけでなく何故このタイミングでautolink.datの更新処理までしなければならないのかという点も検討した方がいいと思いますよ。 --  &new{2006-03-09 (木) 20:25:23};
-- こんにちは :) コードの機能分割については放っておいてもそうなるので大丈夫です。バッファ操作について確認させて下さい。set_file_buffer() を取れと言われていますか、それともさらに細かく追加しろと言われていますか?なおバッファを一旦切ることについては、理解している範囲では必須であるはずです。 -- [[henoheno]] &new{2006-03-11 (土) 21:44:13};
-- コードの機能分割云々は意味がよく分からないのでバッファ操作についてですが、flock処理しているところでは解放前に最低限OS側へデータを渡さねばならないですからバッファリング無しかバッファをフラッシュするかの2択しかないわけですよね。で、このコード周りはバッファ無しの方法を選択してループして一行ずつ出力しているという事なのでしょうが、ファイル出力なので塊にして出力した方がOS側がダーティーデータの管理を省けるのでスループットが上がるという単純な話です((遅延書き込みで無いOSでは他にも遅くなる理由がありますが))。メモリが勿体無いのであればバッファリング有りで解放前にフラッシュするほうを選択すれば良いだけです。ですがこの場合recent.datのファイルサイズは多くて1M程度と見積もっているのでこの場所でデータ連結用の一時変数を使っても構わないと踏んでます。こんなところで良いですか? --  &new{2006-03-12 (日) 00:44:36};
-- いえ。そのバッファ操作(切る場合、フラッシュする場合ですか)のコードをPHPで具体的にどうすべきとお考えなのかを提示していただけますか。 set_file_buffer() に関するコメントもお願いします。recent.datは件数ベースであれば仰る通り量は少ないと思います。メモリの確保と廃棄はflock()を取った後の方が良さそうですね(複数セット確保されるのは勿体無いので)。 -- [[henoheno]] &new{2006-03-12 (日) 01:16:41};
-- 具体的なコードは下のものですよ。set_file_bufferへのコメントとは取るか取らないかでしたらこのケースではサイズからいってどちらでもいいと思いますよ。最後のflockを取った後でというのはよくわかりません。 --  &new{2006-03-12 (日) 01:38:00};
- こんにちは。これで、最初のコメントで、どのように思われて「バッファリング無しに・・・」とコメントされていたのかがやっと判りました :) set_file_buffer() の意味を何か誤解されていませんか。(私が誤解しているなら指摘して下さい) -- [[henoheno]] &new{2006-03-16 (木) 00:22:23};
-- set_file_buffer() は万一のファイルの破壊を防ぐべく、出力バッファをクリアするために挿入された物のはずなので、破壊が起きるシチュエーションが絶対にないなら絶対にいらないし、可能性があるなら絶対に必要です。 -- [[henoheno]] &new{2006-03-16 (木) 00:23:39};
--- [[cvs:Attic/file.php]] (r1.40) -- 当時 [[ぱんだ]]さんがコミットしたコード
--- PHPマニュアル:  [[set_file_buffer()>PHPfunc:set_file_buffer]]
--- [[STACK*:PHP中級>http://www.stackasterisk.jp/tech/php/php02_01.jsp]]
- 私がここで明確にさせていただきたかったのは、「その提案は本当にバッファをコントロールしている事になるのか」と「バッファをさらにコントロールするために何か努力ができるのか、それともできないのか」です。例えば: -- [[henoheno]] &new{2006-03-16 (木) 00:26:31};
-- (1) flock() の直後で set_file_buffer() を使ってバッファを再設定すべきだ。そうすれば問題ない上にさらに早くなる。検証してみたが(以下略)
-- (2) このシチュエーションでは set_file_buffer() は絶対に不要だ。その理由は(以下略)
-- (3) このシチュエーションなら、fflush() をこう使って(以下略)
- 取り扱うデータの量の大小でデータの破壊が左右されるはずがありませんから、それは set_file_buffer() を外す理由にならないと思います。 -- [[henoheno]] &new{2006-03-16 (木) 00:31:28};
- 確認しておいたほうがいいようですので、まず最初に下のコードの意図ですが、fputs()のコール回数を減らす事ではなくset_file_buffer()で停止したバッファリング動作を肩代わりする事に有ります。バッファ動作をコントロール下に置くと言い換える事もできるでしょうが、その場合の選択肢は12日に書いた2種類になります。続いて「万一のファイル破壊を防ぐ」の意味ですが、ここでのファイル破壊は排他処理が完全でなく別プロセスの処理によって引き起こされるものです。決してサーバー側の事故(停電・ディスククラッシュ等)によって起こされる破壊に対するものではありません。 --  &new{2006-03-16 (木) 20:36:55};
- それから、set_file_buffer()はバッファリングを切るために呼ぶのであってバッファサイズの変更などで呼ぶ必要はないでしょう。切らないのであればロック解放前にfflush()を呼ぶ必要があるだけです(何度も書いていますが)。文章だとどこまで理解してもらえているか判然としないので説明が難しいですねぇ…。 --  &new{2006-03-16 (木) 21:21:24};

#comment

----------------
([[BugTrack2/151]])
** コメント: fputs()のコール回数を減らす案 [#wca28f53]
- ファイル書き込みを短縮するだけならこれを試してください(何も削らないが速度は改善するはず)。 --  &new{2006-03-10 (金) 22:04:58};
 --- 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]] &new{2006-03-11 (土) 21:52:03};

#comment

** set_file_buffer($fp, 0) の説明に疑問... [#q21c5f4b]
長文で申し訳ありません.  引用されている set_file_buffer($fp,0) の説明に
疑問を感じたため、手元で確認した結果をこちら記載します.
検証方法の不備や、さらに提供すべき設定内容があれば、お知らせください - [[jjyun]] &new{2006-03-26 (水) 23:36:15};

上には 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 [#z8715497]
  <?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 [#e842d5df]
 <?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 [#v8a2e95a]
 <?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 とその結果 [#i25bf7bf]
 #!//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 とその結果 [#c56c24da]
 #!//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

*** 結論. [#kee64b6f]
『 set_file_buffer() は 書き込みバッファに対する処理であって、
読み込み処理をブロックすることはない.  』
と考えます.
- 中途半端な書き込み中のデータを読み込まないようにするには、write処理と排他できるように flock() することが必要だと思います. -- [[jjyun]] &new{2006-03-22 (水) 23:42:25};
- でも、PHPマニュアルには『flock()はNFS及び他の多くのネットワークファイ ルシステムでは動作しません。』 とあります. このようなシステム上も考慮すべきだと思うのですがいかがでしょうか?&br; flock() を ディレクトリの作成を用いたロジックで置き換えるという記事を見かけたこともありましたが、処理のタイムアウトが起きた場合を憂慮しています. タイムアウト発生時に、作成したlock用のディレクトリの削除を行わせることはできるでしょうか? -- [[jjyun]] &new{2006-03-22 (水) 23:51:58};
-- %%phpのflockはfcntl()が使用可能かコンパイル時に調べて使えるなら優先的に使うようですので、ほとんどのNFSで問題は起きないと思います。%%使うコードは入っていますが優先的ではないようです。確証がないのに書いて大変申し訳ありません。 --  &new{2006-03-23 (木) 18:58:14};
- 結論で言われている事(読み込む側がブロックするわけじゃない)はその通りじゃないかと思うのですが、発端の部分が妙だと思います。 -- [[henoheno]] &new{2006-03-25 (土) 01:38:46};
-- プロセスに対する「ブロック」という用語について。あるプロセスが"ブロックする"と言うとき、それはそのプロセスが(必要な条件が揃うまで)「自身の実行を一旦停止する」(次に動くタイミングはOSなどに任せる)事を意味しています。他のプロセスを「邪魔する」とか、ここでは触れられていないですが「領域」といった意味ではありません。((OSに関する書籍などの、プロセスに関する部分を参照して下さい)) -- [[henoheno]] &new{2006-03-25 (土) 01:39:21};
-- [[set_file_buffer() のマニュアル>PHPfunc:stream_set_write_buffer]] にある通り: PHPでは、プロセスがファイルにデータを出力したい場合は、「各自の出力バッファに収まる分だけ出力した後に"ブロックする"」という挙動を繰り返す作りになっているようです。バッファに書いた内容がいつ実際に書き込まれるのか、というのはPHP本体やOSの機嫌に依存します。 -- [[henoheno]] &new{2006-03-25 (土) 01:40:09};
  「fwrite() による出力は、通常では 8K バイトがバッファ されます。
    これは、もし同じストリームに対し出力を行おうとするプロセスが2つ
    あったとき、 いずれかのプロセスは、他方のプロセスが出力できる
    ように 8K バイト分 データを書き出したところで停止することを
    示しています。」
-- 書き込みの途中でプロセスがブロック(停止)するという事は、その間に別のプロセスが任意の動作をする可能性があるという事を示しています。 -- [[henoheno]] &new{2006-03-25 (土) 01:49:25};
-- (必要があれば明日以降につづく) 別に STACK* で言っていることは間違っているとは思えないし、この件を突き詰めようと言うならば、set_file_buffer()ではなく、その説明にある「書き込み時のブロック」に関してPHPでの実装および挙動を検証するべきではないかと言う事を言いたいのです。STACK*で言っていることを再現する事を狙ったコードと説明でないと納得できません :) -- [[henoheno]] &new{2006-03-25 (土) 01:56:15};
--- ブロックの用語の使い方が誤っている件はすいませんでした. -- [[jjyun]] &new{2006-03-25 (土) 03:10:51};

#comment

トップ   新規 一覧 検索 最終更新   ヘルプ   最終更新のRSS
Site admin: PukiWiki Development Team

PukiWiki 1.5.4+ © 2001-2022 PukiWiki Development Team. Powered by PHP 8.2.12. HTML convert time: 0.021 sec.

SourceForge