読者になるhnwの日記この広告は、90日以上更新していないブログに表示しています。15:00):rename関数について言えば、同一ファイルシステム上であればrenameシステムコールを利用するのでこの問題は起こりません。蛇足ですが、ファイルシステムをまたがってrename関数を利用するとコピーしてから削除することになり、アトミック性を保証できないため、障害の原因にならないかどうかの検討が必要だと思います。PHPのrename()関数はファイルシステム間で使うとメモリをバカ食いする」で指摘されている通り、PHPのcopy関数やファイルシステムをまたがってrename関数を使う場合に、PHPがファイルサイズと同じ大きさのメモリを消費してしまいます。10.5+PHP5.3.3環境では再現しました。count=1024」でファイルを作ってから実行してください。copy("./1gb.dat","./1gb.bak");原因と対策この原因はPHPの実装にあります。copy関数を実現しているコード部分を以下に示します。}(以下略)copy関数を呼び出すと、上記コード中のphp_stream_mmap_range関数でmmapシステムコールを利用してコピー元ファイル全体をメモリ上にマップし、php_stream_write関数の中でwriteシステムコールでコピー先への書き込みを行います。mmapした直後はメモリは消費しませんが、ファイル全体を読み進めるうちにファイル全体がメモリに乗ってしまうため、最終的にファイルサイズと同じだけのメモリを消費してしまいます。これは巨大ファイルを扱うには不向きなやりかたです。そもそも、copy関数の挙動を考えるとファイルの先頭から最後までをシーケンシャルに読み込みつつ書き出して、読み終わったら即座に内容を破棄してしまうわけですから、ファイルサイズにかかわらずmmapを使うメリットはほとんどありません(後述の「mmap(2)のメリットって?」も参照ください)。ところで、php_stream_mmap_range関数の中身を追いかけていくと、ファイルサイズが4MBを超えていたらmmapしないような処理が見つかります。NULL;}このように、mmapしようとするサイズが4*1024*1024=4MBより大きいかどうかチェックしているコードがあります。コメント部分から判断すると、巨大ファイルをmmapして他のプロセスをスワップアウトさせてしまわないようにという意図のようです。copy関数から呼び出された場合にはlengthは0となって先のチェックに引っかからないため、大容量のファイルでもmmapしてしまいます。これは本来の意図と異なる挙動なのではないでしょうか。これを修正し、copy関数から呼び出された場合でも正しいlengthを渡すようなパッチを作成しました。5.3.3で作りましたが、他のバージョンにも適用可能だと思います。php-5.3.3-avoid-mmap-for-largefile-patch.txtこのパッチを利用すると、4MBを超えるファイルはmmapせず、バッファリングしながらのread(2)&write(2)でファイルコピーされるようになります。メモリ消費量も改善されます。mmap(2)のメリットって?ところでmmapで実ファイルを触ると何が嬉しいんだっけ、と改めて考えてみました。次の2つがメリットだという認識でいいんですかね...?ファイルをランダムアクセスする場合に、fseek(3)とかせずに済むので楽チン。一方で、アクセスした場所だけディスクアクセスにいくので、fread(3)などでファイル全体を取得するより効率が良い。カーネル空間からユーザー空間へのメモリコピーが起こらないので、特にファイルキャッシュに乗っているファイルを読み込む場合に性能面でメリットがある。考えると、頻繁にアクセスされる小さいファイルや、ランダムアクセスされるファイルであればmmapを使うメリットがありそうです。copy関数についても、頻繁に同じファイルをコピーするようならメリットがあるかもしれませんが、まずありえない状況だと思います。今回作ったパッチをPHP本家に投げようと思ったのですが、その前に他の人の意見も聞ければ嬉しいと思って記事にしてみました。これじゃだめなの?とか、他の言語ではこうしてるよ、などあれば教えてください。本当はsendfile(2)でファイルコピーするのが一番速いのかもしれませんが、今のPHPの実装にうまく組み込むのは難しそうです。sendfile(2)はソケットに対して書き込みするときしか使えないそうです。@dr_awnさん、ご指摘ありがとうございます。ソケットに対してのみsendfile(2)を使うようにするのは余計に難しいと思います。mmap(2)で適切にmadvise(2)すれば読みおわったところからページアウトしてくれるかと想像していたのですが、手元の環境で観察する限りアクティブでないプロセスをスワップアウトさせる方が優先度が高いようです。mmapでうまくいく方法があればその方が良い気もするんですけどね...。何にせよ、このあたりは他にも問題がありそうな気がします。ソースコードのぞいてみるか、という気になった人がもしいれば、記事にまとめた甲斐があるというものです。2015の3日目です。12はてなブログをはじめよう!hnwさんは、はてなブログを使っています。ブログを報告する引用をストックしましたストック一覧を見る閉じる引用するにはまずログインしてくださいログイン閉じる引用をストックできませんでした。再度お試しください閉じる限定公開記事のため引用できません。