記事紹介: Inside Vista SP1 File Copy Improvements (1)

ZDNet の翻訳記事より.『「Vista SP1」対「XP SP2」ふたたび--Windowsベンチマーク対決第2弾』

最初のベンチマークテストに寄せられた主な批判の中に、ファイルコピーの際、見えない部分でVista SP1とXP SP2の動きが違うという事実を、私が見過ごしているというものがあった。この違いはハードディスクへの書き込み時のキャッシュに由来するもので、XPはファイルをキャッシュするのに対し、Vistaの標準設定では書き込み時のキャッシュは無効になっている。加えて、ファイル転送のプログレスバーの仕様が異なり、XPとVistaでは挙動が異なることが、事態をさらに複雑にしている。Vistaではキャッシュがディスクにコミットされた時点でファイルコピーの経過を示すダイアログボックスが消えるが、XPではキャッシュのコミットが待ち状態であってもダイアログが消える。つまり、XPは速く「見える」ように設定されているのだ。この点について、Mark Russinovich氏が以下に詳しく説明している。

おそらく、このアルゴリズムの最大の欠点であり、多くのVistaユーザーが不満を訴える原因の1つになっているのは、サイズが256Kバイト〜数十Mバイトの大量のファイル群を含むコピーでは、体感上のコピー性能がWindows XPよりかなり悪くなることがある点だ。XPのアルゴリズムではファイルI/Oにキャッシュを用いているため、Explorerは目的ファイルのメモリへの書き込みが終わると、コピーのダイアログボックスを消してしまう。実際にはその後もCache Managerがバックグラウンドでデータをディスクにコミットしており、その処理にはもっと時間がかかる。一方、Vistaのキャッシュを使わないアルゴリズムの場合、Explorerはそれぞれの書き込み処理が完了するまで次の処理の命令を待つことになり、最終的にはコピーするデータがすべてディスクに書き込まれるまで「コピー完了」と表示しない。また、Vistaの場合、Explorerがコピーにかかる時間の予測を始めるまで12秒待つうえ、この予測計算のアルゴリズムはコピー速度の変動に左右されやすく、両者が相まって、ユーザーはXPより時間のかかるコピーにイライラを募らせることになるわけだ。

とにかく、Russinovich氏の記述が事実なら、XPは、信頼性を犠牲にして体感上の性能をアップさせ、ファイルのコピーが高速だとわれわれに信じ込ませていたことになる。それならば、コピーのスピードはいったん横に置いておいて、問題を違う角度から見ることにしよう。大容量ファイルを転送しているときのレスポンスを検討するのだ。というわけで、ファイルコピー操作中のシステムをベンチマークテストにかけることにする。

この記事に関するブックマーク を読んでいると,「何でそんなことするの?」って話が出てました.
確かにこの ZDNet の記事だとだいぶ意味不明かも.というか何のベンチマークを取りたいのかさっぱり分かりませんでした*1.
いずれにせよ,『Inside Vista SP1 File Copy Improvements』の解説記事はいつか書こうと思っていたものです.というか半分ぐらいは書けているのですで,とりあえず書けている部分だけでも置いておきますかね.というわけで以下 "Inside Vista SP1 File Copy Improvements" の副読記事みたいなもの.訳じゃなくて色々順序を変えたり補ったりしてるのでご注意を.とりあえず時間がある方はオリジナルの記事を一読されることをおすすめします.

Cached I/O V.S. Non-Cached I/O

従来のファイルコピーと Windows Vista 無印のファイルコピーの最大の対立軸は,前者が Cached I/O を使用するのに対して,後者が全面的に Non-Cached I/O への書き直しを行ったというところ.ただしオチを先に言ってしまえば Vista SP1 でやっぱり Cached I/O に回帰.「何だ結局失敗か」と言うは易し.彼らが何を学んだか見てみますかね.

Cached I/O の特徴と問題点

元記事ではまずこのあたりを重点的に解説.というわけで「インサイド Windows」のおさらい.
CreateFile でファイルを開くとき,デバイスが Cached I/O をサポートしなかったり,意図的に Cached I/O を回避しない限り,基本的に Cached I/O モードが選択される.
さて,ここで言う Cache の実体は何だろう? リングバッファや STL の queue を挟み込むパイプライン構造を想像した人は残念ながら外れ.
Cached I/O 使用時は,OS が勝手にファイルのメモリマッピングを行っていると考えると分かりやすい.
Cached I/O モードでは,ファイルの該当領域が OS によって内部的に MapViewOfFile される.そして ReadFile や WriteFile は,この内部領域との memcpy として振る舞うことになる.
ファイルから ReadFile でデータを読み取るときの何が起きる? ここで分岐.

  1. 該当領域に対応する物理ページが一切存在しない場合
  2. ワーキングセットに該当領域は存在しないが,物理ページには見つかった場合
  3. ワーキングセットに該当領域が存在した場合
    • 単なる memcpy

ディスクキャッシュがメモリマップドファイルと同じ意味で,メモリマップドファイルが一種の共有メモリであることを知っていれば,先読みキャッシュの実装も簡単に理解可能.別プロセスで同じ領域をメモリにマップして,次に読み込まれそうなページを先にフォールトさせればよい.メモリは共有されているので,これで Explorer から見てもメモリに読み込まれたことになる,と.
以下実演の引用.これは Windows XP でのファイルコピーの流れ.

The copy engine relied on the Windows Cache Manager to perform asynchronous read-ahead, which essentially reads the source file in the background while Explorer is busy writing data to a different disk or a remote system. It also relied on the Cache Manager’s write-behind mechanism to flush the copied file’s contents from memory back to disk in a timely manner so that the memory could be quickly repurposed if necessary, and so that data loss is minimized in the face of a disk or system failure. You can see the algorithm at work in this Process Monitor trace of a 256KB file being copied on Windows XP from one directory to another with filters applied to focus on the data reads and writes:

分かりやすく表にしてみる.

event Process Read range Real disk read Write range Real disk write
1 Explorer 0 ~ 64 KB
2 Explorer 0 ~ 64 KB
3 System 128 KB ~ 196 KB
4 System 196 KB ~ 256 KB
5 Explorer 0 ~ 64 KB
6 Explorer 64 ~ 128 KB
15 Explorer 64 ~ 128 KB
16 Explorer 64 ~ 128 KB
17 Explorer 128 KB ~ 196 KB
18 Explorer 64 ~ 128 KB
19 Explorer 196 KB ~ 256 KB
22 Explorer 196 KB ~ 256 KB
23 Explorer End of File
811 System 0 ~ 64 KB
1175 System 64 ~ 128 KB
1213 System 64 ~ 128 KB
1233 System 196 KB ~ 256 KB

まずイベント 1 と 2 に注目.プロセスは Explorer.Event ID 1 のものがユーザモードの読み取り要求で,Event ID 2 のものがカーネルモードに昇格して物理ディスクからの読み取り処理,と考えればこれは OK.Event ID 2 は同期 I/O で,読み取りが完了されるまでスレッドはブロックされる.
一方 Event ID 3, 4 は何かというと,これらが先ほど述べた「先読み」.System プロセスで事前にページフォルトを起こすことで,あらかじめメモリマップドファイルの中身を埋めてしまっている.当然これらの読み込みも同期 I/O で,ページ読み込みが完了するまでブロックされるんだけど,プロセスが別である Explorer はその間も別の作業を行うことができる.かくして,Explorer から見れば,「バックグラウンドで先読み」が行われているというわけ*2.
続いて Event ID 6, 17, 19 に注目.Explorer からのファイル読み込みだけど,そこは既に System プロセスが読み込んだ場所,なので実際には memcpy + オーバーヘッドのコストしかかからない.先読み速度が適切だと,Explorer からのファイル読み込みは常に memcpy 程度のレイテンシで収まることになる.やったね.
書き込みは逆の流れをたどる.Explorer の書き込み先は,いきなり生ファイルというわけではなくて,まずはメモリマップされたメモリページに書き込まれる.これが例えば Event ID 5, 16, 18, 22.ちょっとしたオーバーヘッドはあるものの,ここでも基本的に Explorer からのファイル書き込みは memcpy 程度のコストになる.つまり WriteFile が成功を返して実際にディスク書き込みが行われるまではずいぶん差がありますよ,と.
じゃあ本当にディスクに書き込まれるのはいつ? というとそれが Event ID 811, 1175, 1213, 1233.ID の値から見ても分かるように,そこそこ時間が経ってるみたい.なぜって同じメモリページに何度も書き込まれる可能性ありますよ? だったらしばらく様子を見て最後の結果のみを書けば書き込みは一回で済んでいいですねと*3.
こうやって見ると Cached I/O ではメモリ共有と割り込みを活用したプロセス間並列処理の一種と言えるかな.Explorer のコードを一切修正することなく,先読みや怠慢な書き出しが外部から注入できているところに注目.例えば WriteFile を全部実行し終わった Explorer は,さっさと終了してしまって構わないですよと.代償は,データを共有するためにメモリを消費するところ.その意味で,Cached I/O はメモリリソースを代償にレスポンスを稼ぐ方向とも言えますな.

Cached I/O だとなんでまずいか?

Windows XP まで,ファイルコピー (Explorer とそのバックエンドである CopyFileEx API) の実装は,Cached I/O を使ってた.そして Vista 開発時に製品開発チームは考えた.

  • 巨大なファイルをコピーするとき,Cache Manager の書き込みスレッドが間に合わないことがあるよ.
    • 最終的には,ワーキングセット以外のメモリを埋め尽くすまでキャッシュを汚染しまくるよ.

XP まではしばしばコピー読みだしの速度に変更済みページの書き出しががついていけないことがあった.そんなところに数百 MB のファイルコピーを行うと何が起きる?
いわゆる「キャッシュメモリ」が余っている限り,次々にメモリマップドファイルの書き出し先に転用されてしまう.もそもとその物理メモリは何かのデータを保持していたわけで,それが例えばメモリマップされた別のファイルであればディスクキャッシュがパージされたことになり,ページファイルと同期済みのヒープの内容であればいわゆる「ページアウト」の完成.(でも手元の XP 環境だと,遅延書き込みせいぜい 20 MB ぐらいまでっぽい.この辺り要調査)
つまり,アプリケーション最小化→しばらく放置 (ページファイルとの同期待ち) →大量のファイルコピーという単純コンボで,ページアウトされたアプリケーションの一丁上がり.
誰もファイルコピーの遅延書き込みがキャッシュを大量に汚染するかもしれないなんて考えもしないよね? でもみんなコピーダイアログのプログレスバーがぐんぐん伸びていくと気持ちいいですよ.多分それが「Think About Emotions*4」
問題は他にもリモートファイルコピーにもありましたと.

  • リモートファイルシステムからファイルをコピーするとき,データが 2 回キャッシュされるよ.
    • ひとつはリモートからファイルが読み込まれるときだよ.
    • もうひとつはローカルにファイルが書き込まれるときだよ.
    • 加えて両方のファイルをメモリマップするのは CPU オーバーヘッドがあるよ.(読み書きに対応してキャッシュマネージャによるメモリマップとアンマップが繰り返される)

SMB 1.0

SMB も問題だよね,と.

  • SMB 1.0 ではパフォーマンスがでないよ.
    • 何が悲惨って……
      • SMB はパイプライニングできないよ.一回パケットを送ったら,成功が返ってくるまで次を送れないよ.
      • パケットサイズが 60 KB だよ.
    • 広帯域で遅延の大きなネットワークのときが一番悲惨だよ

そして Vista RTM 版で何を行ったか,何が行われなかったか

  • Large I/O が使えるようになったよ.
    • 今までの Windows だとディスクへの読み書きが結局 64 KB 単位に制限されてたよ.
    • Vista 無印からもっと大きな単位でディスクに読み書きできるようになったよ.
  • Cached I/O をやめてAsynchronous Non-Cached I/O に移行するよ.
    • Large I/O も併用するよ.
    • でも常に Non-Cached I/O だと色々問題があることがわかったので一部は Cached I/O を併用するよ.

続く

おすすめ本

インサイド Microsoft Windows 第4版〈上〉 (マイクロソフト公式解説書)

インサイド Microsoft Windows 第4版〈上〉 (マイクロソフト公式解説書)


インサイドMicrosoft Windows第4版〈下〉 (マイクロソフト公式解説書)

インサイドMicrosoft Windows第4版〈下〉 (マイクロソフト公式解説書)


Windows® Internals, Fifth Edition (PRO-Developer)

Windows® Internals, Fifth Edition (PRO-Developer)

更新履歴

*1:『[http://builder.japan.zdnet.com/sp/vista-tips/story/0,3800083683,20368908,00.htm:title=もう1つの「Vista SP1」対「XP SP2」ベンチマーク]』という検証記事も出ています

*2:これは CreateFile で FILE_FLAG_SEQUENTIAL_SCAN を使用している場合の挙動.通常は 2 回のアクセス位置から 3 回目を予想するので,最初の 2 回の読み出しは先読みされない(11.7.1 『インテリジェントな先読み』).

*3:11.7.2『書き戻しキャッシングと怠慢なライタ』および 11.7.3『書き込みの抑制』.FILE_ATTRIBUTE_TEMPORARY を使うと本当に追い詰められるか明示的なフラッシュ指示があるまでデータを書き出さなくなる.

*4:Joel が先日言っていたことはまさにこれなんだと思っています. id:matarillo:20080214:p1 とか参考に.