事前に必要メモリサイズを確定させづらい場合のメモリ確保実装(VBuf)について。
FastCopyではコピー動作を開始すると、ディレクトリに潜るたびにのコピー元とコピー先のカレント直下の全エントリ(とファイル属性)を収集・比較し(**2)、コピーが必要なファイルリストを生成しています。普通であれば、1ディレクトリ直下のファイルは多くとも数千ファイル程度で、FastCopyの場合 1000エントリで160KB程度になります。
とはいえ世間は広く、機械生成な環境で1ディレクトリ直下に100万ファイルといった状況もあり、その場合は160MB(追記: ファイル名が長い場合 600MB以上に)のメモリ消費になったりします。(*1) つまり、状況によって1000倍程度メモリ必要量が変化します。といってエントリ毎に malloc()等で動的に確保していった場合は確保/解放処理等のコストが大きくなります(**3)。
このため、FastCopyでは VirtualAlloc(MEM_RESERVE) で仮想アドレス空間だけ大きく予約しておき、足りなくなるたびに n KB(ただしページサイズの倍数)づつ VirtualProtect でメモリを割り当てる仕組みを使っています。
この形のいいところは、
1.リストではなく配列/ベクター的に使えること(**4)。
2.フラグメントを気にしなくても良いこと、確保/解放のコストが低いこと
3.増加時に基底アドレスが変化しないので、余分なコピーが無い&要素内同士でポインタが使えること
といったところです。
ちなみに、UNIX系の場合は mmap(PROT_NONE)で確保した空間を徐々にmprotect()でコミット済メモリを割り付けていく感じにすれば良いかもしれません。(**5)
(*1)だからといって(99%以上の環境では不要な)160MBを毎回確保しておくというのは筋悪ですし、逆に160MBで足りるの?という話に。
(2017/02/22追記)
(**2)このエントリ群に対応するハッシュテーブルも同時に作ることで、高速比較を実現しています。
(**3)1ディレクトリ直下のファイルコピーが終わり、子ディレクトリに潜る等のタイミングで、現ディレクトリ用ファイル情報VBufはクリアされます。
(**4)配列と言っても、stat情報のエントリはファイル名長という可変要素が入るため、個々の要素サイズは変化するため、別途、VBufを使った要素位置を示すポインタ配列も作っています。
(**5)mmap(PROT_NONE)はガードページ的な使い方しかできない模様。(ページ割り当てしない限り、巨大アドレス空間を予約しても、OSからオーバーコミットに見えないような、代わりの方法はないものかしらん?mmap(PROT_NONE)で確保しておいて、必要になるたびにPROTO_NONE領域を縮める方向にmunmap&mmapしつつ、空いた空間に通常mmapする等?)
P.S 現在、64bit版で1GB、32bitで128MBの空間を予約しています。(fastcopy2.iniで変更可)
(追記 2017/08/02) MEM_RESEREVEでアドレス空間だけ確保する場合、PTE/PTDすら不要で、ほぼメモリ消費がない。
Categories
Android |
CeSleep |
comp_misc |
comp_tips |
fastcopy |
ipmsg |
mailman |
misc |
npop |
ScheEdit |
sigsleep |
tdiary |
thinkpad
なお、ディレクトリエントリのファイル名要素が長い場合、1文字につき2byteづつ追加でメモリを消費します。
Mac OS X移植(RapidCopy)ではmmapでいきなりPROT_READ | PROT_WRITEで派手に一括確保してVBuf::Growはサイズ拡張したふりをするだけの実装にしました。(恥ずかしながらmprotectを知らなかった)
OS Xのリソースモニタで見る限りはいきなりガバッと実メモリを取るわけでもないようなので、よしとしちゃいましたが
mprotectするべきだったのかもと思いました。