ユーザの感情に作用する遅延書き込みの仕組みを暴け - 記事紹介: Inside Vista SP1 File Copy Improvements (3)

前回記事

それではいよいよ最終回にしてメインディッシュ.記事紹介から離れてこの件を深く掘り下げていきます.

Cached I/O vs Non-Cached I/O revisit

Cached I/O をオートマ車に喩えるなら,Non-Cached I/O はまさにマニュアル車です.
両者の大きな違いは,どれぐらいのサイズのデータを,どれぐらいの期間メモリ上に保持するかを自動で決めるか手動で決めるかというところです.もちろん後者の方が「理想的な状況」でのパフォーマンスは上になります.似たような対立軸はあちこちにありますね.

Cached I/O Non-Cached I/O
参照型作りまくりで寿命管理は GC にお任せ 値型配列で寿命管理は自前実装
DrawUserPrimitive DrawPrimitive
D3DPOOL_MANAGED D3DPOOL_DEFAULT

んで,こんな論文があるのですよ.

second author に注目.RDBMS 界の大御所にして,Microsoft Research の Jim Gray 博士*1です.なぜ氏が .NET の論文を? という方は軽く論文のグラフを眺めてみて下さい.基本的にこの論文は Windows OS で Cached I/O と Non-Cached I/O のパフォーマンスを比べる話です..NET や P/Invoke の話は半分おまけっぽいですね.
余談ですが Microsoft SQL Server という製品は面白くて,Non-Cached I/O や Scatter/Gather I/O,Fiber, NUMA など非常に特殊な API をやたらに活用しています.むしろ MSSQL Server チームからの要請でこれらの OS の機能追加が後押しされたという見方すらできるかもしれません.
さて,論文の話に戻りましょう.

As shown in Figures 6 and 7, a file striped across 16 disk drives delivers 800 MBps and uses about 30% of a processor – when those experiments are done with buffered IO the speed is dramatically less – about 100 MBps vs 800 MBps – so for now, the .NET runtime is OK for single disks, but un-buffered IO is needed to drive disk arrays at speed..

Non-cached I/O なら CPU 使用率 30% で 800 MBps 出ていた環境で,(.NET のFileStream クラスのバッファも含めた) cached I/O を使うと 100 MBps しか出ませんでした,という話です.
この話と,「従来の Windows のファイルコピーは cached I/O を使っていた」という話を組み合わせると,「Vista で Non-cached I/O に移行します」と聞いてもそれほど驚きはしないでしょう.
しかし現実にどうなったかというと,確かに移行はしたのですが,期待は絶望に変わりました.私を含む多くの人々にとっての印象は「Vista のファイルコピーは非常に遅くなった」というものだったのです.
もっとも,複数のシナリオを総合的に計測して,本当に「Vista のファイルコピーは非常に遅くなったかどうか」を調べた人は少ないのではないでしょうか.ある特定のシナリオ,例えば複数のファイルをネットワーク越しに取ってくる,といったケースで,以前よりもダイアログの伸びが遅いのに気がつき,その悪印象が「全てのファイルサイズ,全てのコピーシナリオできっと遅いに違いない」という確信に繋がった人も少なくないはずです.
これは全くもって感情的な反応です.しかしだからこそ,Joel があれほど口を酸っぱくして警告しているのでしょう.現在のソフトウェア制作では,制作者と利用者が全ての面で異なる場合がほとんどです.故に,誰がそのソフトウェアを使用し,そのときにどんな感情を抱くかという要素は決して無視できるものではありません.

現実は予想の斜め上を行く

また,現実の多様性という意味でもこの件は面白いものでした.一見ヘマをしているようで,それが実はうまく行っていることもある,というわけです.

技術者:「そもそも 1 TB のハードディスクを,1 GB の空きメモリでキャッシュするとしたら,選べるデータは全体の 0.1 % しか無いんだ.そんな貴重なキャッシュ領域を,単に今 数百 MB から 数 GB のファイルをコピーしているからという理由で丸々書き換えてしまなんて,まさにキャッシュの汚染だよね!」
悪魔:「本当にそうかな? 確かにファイルコピー直後のファイルを開くことは少ないように思えるかもしれないけれど,例えばデスクトップにファイルをコピーしたときのことを考えてみなよ.だってデスクトップに新しいファイルが現れると,デスクトップサーチのインデックス作成が始まって,アンチウィルスソフトやアンチスパイウェアソフトがスキャンを始めて,Explorer は親フォルダのフォルダアイコンに表示するサムネイルの作成を始めるんだぜ?」

うちの日記ももっともらしい話ばかり書いているので,今回の件なんかは本当に身のすくむような話です.

システムとしての複雑さと,個としての単純さ

例えば ZDNet の記事に対する反応.

ある意味ショッキング。

XPではキャッシュのコミットが待ち状態であってもダイアログが消える Vistaではキャッシュがディスクにコミットされた時点でファイルコピーの経過を示すダイアログボックスが消える

つまり、XPは処理が途中まですんだら、ダイアログを消しちゃうので、そのせいでXPが速く見えてたと。Vistaがなんでもかんでも悪いわけではなさそうです。

確かに普通そう思いますよね.私も最初記事を読んだときは,「うーむ,なんてインチキをしていたんだ」と思いました.が,実験してみると……? 続きはあとの方で.
さて,ここからは感情ではなく現実の挙動の方に再び目を向けます.
現実は複雑だと先ほど書きましたが,面白いもので,注意深く実験すると,個々のアルゴリズムの振る舞いが見えてきて,しばしばそれはドキュメント通りの非常に素直な動きだったりします.
この驚きは以前のエントリでも書いたかもしれません.大量のヒープを消費するアプリケーションを最小化したら,その直後からページファイルとの同期が始まるという話ですが,私自身,試すまでは「そんな単純に動いてるもんかいな.もっと複雑なアルゴリズム使ってるんじゃないの?」と思っていたのです.ところが実際試してみると,『インサイド Windows 第4版』の内容から予想される動作そのもので,逆に「ほんとかいな」と疑いたくなってしまうぐらいでした.
ポイントはここにあります.確かに,システム全体としてのパフォーマンスや,そのパフォーマンスがどのような感情を利用者に抱かせるかは,非常に複雑な問題です.事前に読み切るのはとても大変です.Microsoft ですらヘマをします.しかし,そんな複雑なシステムでも,部分部分は,それほど複雑な仕組みで動いている訳ではないのです.
そして,Windows を構成する個々のコンポーネントの振る舞いについての資料は探せば結構あるものです.ドキュメントが公開されている以上,ソースコードが公開されていないことは言い訳にはなりません.必要なコストをかければ,限定された範囲内での動作推定はかなりの高精度で可能です.これは魔法ではなくて技術です.そこのところはよく憶えておきましょう.
確かに難易度は極悪です.悪意に基づいたとしか思えない偶然と過去が折り重なり,伝聞で伝わってくる情報はミスリードを誘うランダムノイズ.
しかしそんな思考迷路も,非常ドアから裏に回ってみると,案外たいしたことのない張りぼてだったりするものです.それでは解をごらんあれ.

遅延書き込み入門

というわけで今回は,Cached I/O による遅延書き込みに絞って,普段はあまり表にさらされない舞台裏をご覧にいれましょう.最後まで読んでいただければ,少なくとも即死コンボを食らわない程度には耐性が付くはずです.
参考資料としては,次のものがお勧めです.

WriteFile が遅延書き込みを行うとき,引数に渡されたデータを単にファイルマッピングされた物理メモリにコピーされただけで,実際のファイル書き込みは完了していません.まずはこれを再現します.
次のようなテストコードからスタートしましょう.これは,

  1. CreateFile API を使用してシステムドライブにファイルを新規作成.
    • デフォルトのままなので Cached I/O を使用することになる.
  2. 64 KB のバッファを VirtualAlloc で確保
    • 適当にデータを埋める
  3. WriteFile API に先ほどのバッファを渡してデータを書き込む
    • これを 128 回繰り返す
    • 合計 8 MB 書き込まれる
  4. CloseHandle でファイルを閉じて終了する

というものです.
手元の環境で,WriteFile 128 回に要する時間を計ってみました.所要時間は約 10 msec 前後で,WriteFile のスループットは約 800 MB/sec に相当します.さらに,CPU の動作クロックを半分にして試してみると,概ね所要時間は 2 倍に,つまりスループットは半分の 400 MB/sec 前後となりました.これが生のディスク速度とはとても思えませんよね? HDD 自体に搭載されているキャッシュメモリを疑うのであれば,他のディスクで試してみるとよいでしょう.なお,実際にディスクに書き込まれるまで同期的に待つことも可能で,それには FlushFileBuffers API を使用します.FlushFileBuffers にかかる時間から,どれぐらい遅延書き込みが溜まっているのか見積もることが可能です.
改めてソースコードを示しましょう.

#define WINVER 0x500
#define WIN32_LEAN_AND_MEAN
#include <windows.h>
#include <iostream>
#include <fstream>

int main()
{
    HANDLE file = ::CreateFileW(
        L"C:\\test.dat",
        GENERIC_WRITE,
        0,
        NULL,
        CREATE_ALWAYS,
        FILE_FLAG_SEQUENTIAL_SCAN,
        NULL);

    const int totalSize = 1024*1024*8;
    const int bufSize = 1024*64;
    char* buf = (char*)VirtualAlloc(NULL, bufSize, MEM_COMMIT, PAGE_READWRITE);
    for(int i = 0; i < bufSize; ++i)
    {
        buf[i] = i % 255;
    }
    int numWriteBytes = 0;
    for(int i = 0; i < totalSize/bufSize; ++i)
    {
        DWORD size = 0;
        const BOOL ret = WriteFile( file, buf, bufSize, &size, NULL );
        if( ret == 0 )
        {
            const DWORD lastError = GetLastError();
            std::cout << "WriteFile failed error:" << lastError << std::endl;
            break;
        }
        numWriteBytes += size; 
    }
    CloseHandle(file);
    VirtualFree(buf, 0, MEM_FREE);

    return 0;
}

ぱっと見ただけでは,これはいかにも「HDD の書き込み速度の影響を受けそうなコード」です.が,そこは既に思考迷路の入り口です.
既にこのコードがほとんど CPU のベンチマークになってしまうこともあるのは示しました.些細なことかもしれませんが,ソースコードに FlushFileBuffers API がみあたらないことなどにも気付いておきたいところです.
このように,ソースコードの字面から,実際にどのような挙動が導かれるか,その可能性を真剣に考えてみるのが本エントリの趣旨です.

遅延書き込みとカーネル変数 CcDirtyPageThreshold

先ほどのテストコードでは,合計 8 MB のデータが遅延書き込みされるのを見ました.テスト環境の HDD の書き込み速度は概ね 20 MB/sec 程度なので,全力で書き込んで 0.4 秒程度のデータがキャッシュされていることになります.
このサイズはどこまで増えるのでしょうか?実は,キャッシュマネージャが保持できるダーティーページ (ディスクに書き込みが完了していないメモリページ) は,カーネル変数 CcDirtyPageThreshold までと制限されています*2
CcDirtyPageThreshold は,搭載物理メモリ量等 によって初期化時に決定される閾値で,キャッシュマネージャが保持できるダーティーページの最大値を現します.
キャッシュマネージャが保持しているシステム全体のダーティーページが CcDirtyPageThreshold を超えそうになると,WriteFile API 呼び出しが内部でブロックされるようになります*3.ダーティーページの減少速度は,HDD の本当の書き込み速度を超えられませんから,これ以降,平均の書き込み速度は HDD の実際の書き込み速度とほぼ一致すると予想されます.
試しに CcDirtyPageThreshold が 510 MB という環境で実験してみました.Cached I/O モードで 64 KB の WriteFile を繰り返し,書き出した合計量と所要時間の関係を測定しながら,合計 1 GB まで書き込んでみるというものです.
結果を下に示しましょう.面白いぐらいに 510 MB ちょっとのところで「モードが変わって」いるでしょう? 前半の書き込みが「終わる」まで約 1.5 秒,その後約 50 秒かけて後半の書き込みが「終わり」ます.しかしディスクはまだ書き込みを続けていると.全てが組み合わされば立派な思考迷路のできあがりです.

もちろんこのグラフの始点と終点を結べば平均書き込み速度が分かりますよね.でも,そんなものに,はたして何の意味があるのでしょうか?

実際の CcDirtyPageThreshold はどれぐらいなのか?

さて,話を戻しましょう.

つまり、XPは処理が途中まですんだら、ダイアログを消しちゃうので、そのせいでXPが速く見えてたと。Vistaがなんでもかんでも悪いわけではなさそうです。

Windows Vista RTM 版以外,つまり Windows XP を含む従来の Windows や,Windows Vista SP1 などは,原則的に Cached I/O を使用してファイルコピーを行っていますから,ダイアログが消えたタイミングでだいたい CcDirtyPageThreshold と同程度の未書き込みデータがメモリ上に残っていると考えられます (もちろんこれはシステム全体に適用される値なので,別プロセスが Cached I/O を行っていれば未書き込みデータはもっと少なくなります).一方で,Windows Vista RTM 版では,Non-Cached I/O を使って,自前でバッファ管理を行いながらファイルコピーを行うので,ファイルコピーダイアログの終了時には基本的に全ての書き込みが完了しています.
さて,実際の CcDirtyPageThreshold の値はどれぐらいなのでしょうか? 『インサイド Windows 第4版 11.7.3 書き込みの抑制』に,WinDbg を用いて CcDirtyPageThreshold の値を表示する方法が解説されています.WinDbg でカーネルデバッグを開始して,!defwrites コマンドを実行します*4
以下は手元の Windows Vista SP1 環境で, 1 GB のファイルをコピーしながら !defwrites を実行したときの結果です.
"writes may be throttled" と表示されていて,書き込みの抑制が行われていることが分かります.

lkd> !defwrites
*** Cache Write Throttle Analysis ***

GetUlongFromAddress: unable to read from 00000000
GetUlongFromAddress: unable to read from 00000000
	CcTotalDirtyPages:                 65449 (  261796 Kb)
	CcDirtyPageThreshold:              65461 (  261844 Kb)
	MmAvailablePages:                 180063 (  720252 Kb)
	MmThrottleTop:                         0 (       0 Kb)
	MmThrottleBottom:                      0 (       0 Kb)
	MmModifiedPageListHead.Total:      75783 (  303132 Kb)

CcTotalDirtyPages within 64 (max charge) pages of the threshold, writes
  may be throttled

Check these thread(s): CcWriteBehind(LazyWriter)
Check critical workqueue for the lazy writer, !exqueue 16
Cc Deferred Write list: (CcDeferredWrites)
  File: 860c22b0 Event: beb6aa98

いくつか環境で CcDirtyPageThreshold の値を調べてみました.

OS LargeSystemCache Memory [MB] CcDirtyPageThreshold [MB]
XP SP2 (x86) 0 256 22
XP SP2 (x86) 0 384 22
XP SP2 (x86) 0 512 22
XP SP2 (x86) 0 768 22
XP SP2 (x86) 0 1024 22
XP SP2 (x86) 0 1536 22
XP SP2 (x86) 1 256 200
XP SP2 (x86) 1 384 311
XP SP2 (x86) 1 512 447
XP SP2 (x86) 1 768 510
XP SP2 (x86) 1 1024 510
XP SP2 (x86) 1 1536 510
Vista SP1 (x86) 0 512 64
Vista SP1 (x86) 0 768 96
Vista SP1 (x86) 0 1024 128
Vista RTM (x86) 0 1536 192
Vista SP1 (x86) 0 1536 192
Vista SP1 (x86) 0 2048 256
Vista SP1 (x86) 1 512 64
Vista SP1 (x86) 1 768 96
Vista SP1 (x86) 1 1024 128
Vista RTM (x86) 1 1536 192
Vista SP1 (x86) 1 1536 192
Vista SP1 (x86) 1 2048 256

なお,Windows XP の LargeSystemCache はデフォルトで 0 で,『コンピュータのプロパティ』にて「プロセッサのスケジュールとメモリ使用量」で「バックグラウンドサービス優先」「メモリ使用量」で「システム キャッシュ」を選択することでも 1 になります.上の表に示したように,手元の Windows Vista SP1 では,LargeSystemCache による CcDirtyPageThreshold の変化は見られませんでした*5
さてさて,この CcDirtyPageThreshold の数字の大きさ,いかがでしょうか?
2 GB のメモリを搭載した,手元の Windows Vista SP1 は,システムディスクの実際の書き込み速度が 20 MB/sec 前後なのに対して,CcDirtyPageThreshold は約 256 MB です.ということは,ファイルコピーは約 13 秒ぐらい「早く」終了しているのかもしれません.もちろんこの間停電が起きれば未書き込みのデータは死亡なわけですが.
Windows XP SP2 ではどうでしょう? これは LargeSystemCache の値による変化が大きすぎて,同じハードウェアとは思えない違いが現れます.レジストリエントリをひとつ変えるだけで,1.5 GB のメモリを搭載しながら 22 MB の CcDirtyPageThreshold ということもあれば,768 MB の環境の CcDirtyPageThreshold が 510 MB ということもあるわけです.もちろん,最大 510 MB の遅延書き込みが許可されるといっても,実際に全て使い切れるかどうかは空きメモリ次第です.結果として,ある日のファイルコピーはとても速く感じられるかもしれませんし,ある日のファイルコピーは HDD 性能に見合った普通の速度に感じられるかもしれません.

なぜ Windows Vista RTM でファイルコピーは遅くなったのか?

以上のように,デフォルト設定の Windows XP SP2 の CcDirtyPageThreshold は十分小さく,標準的な HDD にしてもせいぜい 1 秒程度だったことが分かりました.おや,何か変ですね.元々遅延書き込みで大して時間短縮が起きていないのなら,Vista RTM 版でその効果が 0 になっても影響は少ないはずです.というわけで,個人的はやはり Vista RTM 版でのヘマはやっぱりミスと呼べるものだったんじゃないかなと思います.Mark 氏の元記事では,他にも様々な要因が解説されていますので,より影響の大きかった原因を探るにはそちらを見直すことになるでしょう.詳しくは本シリーズの第 1 回と第 2 回をどうぞ.
ただし例外として,XP SP2 環境で LargeSystemCache を 1 に設定していた人たちがいます.彼らは XP SP2 時代に,遅延書き込みで大きな時間短縮効果を得ていた可能性があります.そんな彼らが Vista RTM に「アップグレード」してしまうと,CcDirtyPageThreshold によって得られていた数百 MB 単位の下駄を失ったことでしょう.この場合に限れば,Vista RTM 版では 256 KB 以上のサイズのファイルコピーに Cached I/O を使用しなくなったことが,ファイルコピー速度を低下させたという説はだいぶもっともらしく聞こえます.
いずれにせよ,Vista SP1 で全てのサイズのファイルコピーは Cached I/O に戻りました.全ては元通りです.再び CcDirtyPageThreshold は常にファイルコピー時間へ影響を及ぼすようになりました.そして大多数の XP ユーザー (つまり LargeSystemCache の値を変えていない人) が Vista SP1 に移行したとき,CcDirtyPageThreshold は大幅に増加します.もちろん実際のファイルコピーには複雑で,コピー元からの読み出し速度も関係してきますし,遅延書き込みにしても常に空きメモリとの相談ですから,単純にコピー時間が影響を受けるかどうかはケースバイケースですけど.
とまあファイルコピーという非常に単純な作業ひとつとってみても,「Vista にして速くなったよ」「遅くなったよ」「変わらないよ」という会話が入り乱れる理由の一端が見えてきたようです.個々のルールはシンプルですが,環境に起因するパラメータの組み合わせが大きすぎて,これはちょっと神ならぬ人の手には余るように思います.
世界は広く,組み合わせは無限に試行され続けます.ある人の環境で発生した組み合わせは,本当にその場限りの奇跡のような組み合わせかもしれません.
かくして,統一的に「Vista は速いか遅いか」を語るのはもはや不可能ということになります.そもそも環境の多様性が大きすぎるのが問題なわけですから,平均に意味があるかどうかすら怪しいものです.多様な環境の平均を取ったとしても,それであなたの「現実」が説明できるでしょうか?
感情を考慮するという観点で言えば,OS が嫌われないためには,「明らかにヘマをした」と思われるような目立ったヘマをしないことに尽きます.この点で Vista RTM 版は大きなミスを犯しました.
あるいは「明らかにうまくやっている」ように見える何かを気付かれる程度にさりげなく示すのは意味があります.SuperFetch は,体感できる時間差を稼ぎ出しうるという点で,私の印象としてはうまくやっているように思います.ただまあ今の SuperFetch は,HDD のアクセス音やアクセスランプとして動作が見えてしまいますから,そこを隠して,つまり SSD などへの移行で光も音もなくなってはじめて,真に空気のような機能に昇華されることでしょう.
ミスを避けて,ワンポイントを気付かれる程度にさりげなく,いわばそういった空気感こそが現代のソフトウェアに求められているのかもしれません.

遅延書き込みを確認するその他の方法

キャッシュマネージャを経由した遅延書き込みを確認する最も確実な方法は,WinDbg の !defwrites コマンドを指定して,CcTotalDirtyPages カーネル変数の値を確認することです.この値が有意に大きければ,未書き込みのデータをキャッシュマネージャが抱えていることが分かります.
WriteFile を行っているソースコードを修正できるのできるのであれば,FlushFileBuffers API を呼んでみるのもひとつの方法です.FlushFileBuffers から戻るまでに時間がかかれば,それだけ遅延書き込みが溜まっていたことが分かります.
Mark 氏の記事にあったように,Process Monitor を利用して実際の書き込みをモニタするという方法もあります.
その他,パフォーマンスカウンタからもある程度のことが分かります.

  • Cache カテゴリ
    • Lazy Write Flushes/sec
    • Lazy Write Pages/sec
  • Memory カテゴリ
    • Modified Page List Bytes (Vista 以降のみ)

遅延書き込みが使えないとき

さて,いくつか実験していて気付いたのですが,WriteFile API に渡すバッファサイズによって,WriteFile が同期書き込み (実際にディスクに書き込むまで戻ってこない) になることがあるようです.このことに気付かないと,不思議な挙動に悩まされることになります.
例えば WriteFile は,カーネルモードへの遷移を伴いそうなので,回数を減らした方がパフォーマンスが上がると予想したとしましょう.そこで WriteFile に渡すバッファサイズを 64 KB から 1 MB に増やしてみます.これで WriteFile の呼び出し回数は 1/16,しめしめと思いきや,1 MB のバッファを渡したときの WriteFile は,遅延書き込みをしなくなって,実際にドライバにデータを渡しきるまで処理が戻らなりましたと.しかも CcDirtyPageThreshold 関係なしに,です.こうなると,書き込み時間は短くなるどころか数十倍も長くなってしまいます.わお!
いくつかの実験から,筆者の手元の環境では 256 KB までの WriteFile であれば遅延書き込みが行われるらしいことが分かりました.もちろんこの値は環境や設定によって異なるかもしれません.重要なのは,64 KB の WriteFile 16 回と,1 MB の WriteFile 1 回が,同じ結果になるとは限らないとことです.
もう一点の気付いたことと言えば,ディスク書き込みポリシーの影響です.Windows Vista SP1 環境で実験していて,USB ドライブの書き込みポリシーで「クイック削除のために最適化する」を選んでいるときのことです.WriteFile 自体は遅延書き込みを行っているように振る舞うのですが,CloseHandle でファイルを閉じるときに FlushFileBuffers 相当のことを行っているらしく,やたらファイルを閉じるのに待たされることになります.CcDirtyPageThreshold が数百 MB という環境では,10 秒どころじゃなく待たされることもあり,これも知らないとびっくりという現象かもしれません.
じゃあ CloseHandle せずにプロセスを終了したらどうなるかも実験してみましたが,結局プロセスクリーンアップ時に同じことが行われるようで,10 秒以上プロセスの終了が待たされることになりました.
書き込み先のディスクの書き込みポリシーを「パフォーマンスのために最適化する」に変更したところ,遅延書き込みデータが残っていても CloseHandle で待たされなくなりました.
これも,Cached I/O の実験をしていたから原因に気づけた話で,単体でこの現象に出会っていたら原因究明に時間がかかっていたかもしれません.だって普通,何百 MB もの書き込みがキャッシュされているなんて思いませんものねぇ?

まとめ

  • 標準の Windows XP SP2 環境では,大量にメモリを搭載しても,CcDirtyPageThreshold は最大で 22 MB 程度に固定されているようです.この程度なら,遅延書き込みの影響はそれほど大きくありません.
    • デフォルト設定の Windows XP SP2 が,遅延書き込みによってコピー時間が短く見えていたという説はちょっと怪しいです.22 MB 程度の遅延書き込みで稼げる時間と言っても,せいぜい 1 秒でしょう.
    • Windows Vista RTM 版では,256 KB 以上のサイズのファイルコピーに Cached I/O を使いません.この場合は,ファイルコピーと CcDirtyPageThreshold は無関係です.コピー完了時には基本的に書き込みも終わっています.
    • Windows Vista SP1 では,全てのサイズのファイルコピーが Cached I/O に戻りました.ファイルコピー時の遅延書き込みの影響は,再び,全てのファイルサイズでも起きるようになります.しかも CcDirtyPageThreshold のデフォルト値が増えているので,影響を受けるユーザの数・規模ともに拡大しているでしょう.
      • ノート PC のように低速な HDD を搭載していて,かつ大容量のメモリが利用できる場合,実際の書き込み完了の 10 秒以上前にファイルコピーダイアログは終了しているかもしれません.
      • パフォーマンスカウンタやカーネルデバッガで遅延書き込みの様子を知ることができます.気になる人はチェックしてみましょう.
  • Windows Vista になって (XP のデフォルトに比べれば) CcDirtyPageThreshold は一般に大きくなったようです.今後は,デスクトップアプリケーションにとっても,遅延書き込みの影響は無視できないものになるでしょう.
    • パフォーマンスチューニングと称して LargeSystemCache を 1 にしている XP ユーザの環境では,既に数百 MB 規模の CcDirtyPageThreshold が使われていた可能性が強いです.
    • パフォーマンスチューニングと称して LargeSystemCache を 1 にしている Vista ユーザは,単に気休めなだけという可能性があります.
    • ファイル書き出しサイズが CcDirtyPageThreshold と同程度の場合,見た目の所要時間を比較したベンチマークは CPU 速度と強い相関を持つと考えられます.CPU がより速く,CcDirtyPageThreshold がより大きい環境の方が,一般に「所要時間」では有利です.場合によっては,HDD 性能とはほとんど相関が見られないこともあるでしょう.
    • CcDirtyPageThreshold が数百 MB に届くような環境では, FlushFileBuffers を行ったり,「クイック削除のために最適化する」を選択したドライブでファイルをクローズするときに,数十秒単位のブロックが発生するかもしれません.GUI スレッドを意図せず長時間止めてしまい,ユーザに「ハングした」と思われないような注意が必要でしょう.
  • 遅延書き込みによるレスポンス向上を狙っている場合,WriteFile に渡すバッファサイズを大きくしすぎると逆効果の可能性があります.
    • 64 KB は恐らく大丈夫で,もしかしたら 256 KB でも OK かもしれません.
  • Windows は原則的にファイルコピーの書き込みで Cached I/O を使用します.ファイルコピーダイアログ終了時には,最大で CcDirtyPageThreshold 程度のデータが未書き込みの可能性があります.例外が Windows Vista RTM 版で,ファイルサイズが 256 KB 以上の場合は Non-Cached I/O を使用します.
    • 平均速度ばっかり見せられるベンチマーク記事 / Vista SP1 レポート記事にご注意を.
    • Vista RTM 版とその他の Windows の所要時間を並べて示した場合,UI 的には同じ値を比べているように見えますが,実際の動作的には意味が異なるグラフを同列に並べている可能性があります.
  • .NET Framework の FileStream クラスもデフォルトでは Cached I/O を使います.
    • となれば,64 KB 単位の書き込みと,1 MB の単位の書き込みでパフォーマンス特性が全然違うかもしれませんね.この挙動を API よりも上の層だけで説明しようとすると思考迷路に囚われることでしょう.

おまけ

図解『Cached I/O 使用時のファイル書き込み時間推定方法』

更新履歴

  • 2008年3月6日
    • Windows XP SP2 と Windows Vista RTM 版で,ファイルコピー体感速度が違うことの説明をちょっと訂正.Vista RTM 版ではファイルコピーが Non-Cached I/O だったのでした.
    • メリット目立たせるのも空気じゃない気がしてきたので「気付かれる程度にさりげなく示す」に文面変更.
  • 2008年3月7日
    • Windows Vista RTM 版でも,256 KB 以下のファイルコピーで Cached I/O を使っていたのを忘れていたので修正.詳細なアルゴリズムについては第二回参照のこと.
  • 2008年3月10日
    • 過去の関連記事を追加.
  • 2009年3月16日
    • "「メモリ使用量」で「システム キャッシュ」"と書くべきところで"「プロセッサのスケジュールとメモリ使用量」で「バックグラウンドサービス優先」"と書いていたのを修正.

*1:1 年前の行方不明事故は記憶に新しいところです.この論文,1997 年にも同じような実験を行っていて,それと 2004 年の比較も行っています.恐らく次もあると期待していますが,そこに氏の名前が載らないであろうことは本当に哀しいですよぅ.

*2:『インサイド Windows 第4版 11.7.3 書き込みの抑制』または『[http://www.i.u-tokyo.ac.jp/edu/training/ss/lecture/new-documents/Lectures/15-CacheManager/CacheManager.pdf#Page=33:title=Cache Manager に関する講義資料の 33 ページ目]』

*3:もちろん,Non-Cached I/O,つまり CreateFile で FILE_FLAG_NO_BUFFERING を付けた場合は,キャッシュマネージャは介在しませんから,この影響は受けません

*4:Windows Vista でカーネルデバッグを行うには,OS 起動時にオプションが必要です.ご注意下さい.id:NyaRuRu:20071016:p1

*5:Vista では『コンピュータのプロパティ』に上記設定項目は無くなっていますし,『[http://blogs.technet.com/askperf/archive/2008/02/01/ws2008-upgrade-paths-resource-limits-registry-values.aspx:title=WS2008: Upgrade Paths, Resource Limits & Registry Values - Ask the Performance Team]』によれば,Windows Server 2008 でLargeSystemCache は Not Used とのことです.NT Kernel 6.0 以降は LargeSystemCache は使われていないのかもしれませんね.