WSL2 の /mnt/c に1000ファイル置いたら、ext4 の457倍かかった
この記事の見出し
WSL2 で1000個の4KBファイルを作成して読み、stat を取り、全部消すまでを測ったら、VHDX 内の ext4 は70.73ms、/mnt/c は32305.75msだった。比は456.75倍。どちらも同じ Surface Pro 11、Snapdragon X Elite X1E80100 上の Ubuntu で実行している。
2026年7月21日に WSL2 aarch64 の初回起動 を測ったとき、短いコマンドは初回だけ15.35秒かかった。今回は起動後の話だ。Linux のプロジェクトをどこへ置くかで、同じコマンドの待ち時間がどれほど変わるのかを実際に確かめた。
比べたのは裸のSSD同士ではない
片方は /tmp/arm64-lab-wsl-fs-bench。findmnt では /dev/sdd、ファイルシステムは ext4、マウントオプションは rw,relatime,discard,errors=remount-ro,data=ordered と出た。Windows のNTFS上にあるWSL2のVHDX、その中のext4であって、LinuxをSSDへ直接入れた構成ではない。
もう片方は、このリポジトリの .bench/wsl-fs-work をWSL側から見た場所だ。実パスはCドライブ上で、OneDriveの同期フォルダには置いていない。WSLでは /mnt/c の下に見え、findmnt の種別は 9p、sourceは C:\、オプションには aname=drvfs と msize=65536 が入っていた。
この確認を入れたのは、最初に「/tmp と /mnt/c を比べればよい」と雑に書き始めたからだ。/tmp がtmpfsなら比較そのものが変わる。実行時にファイルシステムを検査し、想定外なら止める形へ直した。
1000個を6ラウンド
計測スクリプトは scripts/wsl_fs_smallfiles_bench.py に置いた。Windows側の ARM64 Python 3.12.10 から wsl.exe を呼び、同じスクリプトを Ubuntu の aarch64 Python 3.12.3 で再実行する。
python scripts/wsl_fs_smallfiles_bench.py --json .bench/wsl-fs-smallfiles.json
1ファイルは4096バイト、総数は1000個、ディレクトリは20個に分けた。各ラウンドで作成、直後の読込、stat、削除を順に実行する。1回ずつウォームアップしてから6ラウンド測り、ext4が先の回と/mnt/cが先の回を3回ずつに揃えた。
読んだバイト数は4096000バイト、stat の合計サイズも4096000バイト、CRC32は全ラウンドで 586cb588。削除後に作業ディレクトリが消えたことも確認している。速かったが一部を書いていなかった、という成功扱いにはしていない。
中央値は70.73ms対32305.75ms
操作別の中央値を並べる。読込は作成直後なので、どちらもcache-hotだ。
| 操作 | VHDX内ext4 | /mnt/c 9p |
/mnt/c側の倍率 |
|---|---|---|---|
| 1000ファイル作成 | 47.61ms | 17183.19ms | 360.92倍 |
| 直後に全ファイル読込 | 10.41ms | 5981.62ms | 574.60倍 |
1000回の stat |
2.25ms | 4696.19ms | 2087.20倍 |
| 1000ファイル削除 | 9.88ms | 5344.51ms | 540.94倍 |
| 作成から削除まで | 70.73ms | 32305.75ms | 456.75倍 |
相対差が最も開いたのは、データ量の少ない stat だった。ext4の2.25msに対して、/mnt/c は4696.19msで2087.20倍。絶対時間は読込の5981.62msより短いが、中身を転送しない属性確認だけでも約4.7秒かかった。この形は、依存ファイルを大量に調べるGitやパッケージマネージャーと似ている。
全ラウンドも残す。
| ラウンド | ext4 合計 | /mnt/c 合計 |
|---|---|---|
| 1 | 72.50ms | 39006.63ms |
| 2 | 74.92ms | 45175.41ms |
| 3 | 71.36ms | 27170.14ms |
| 4 | 65.22ms | 31380.33ms |
| 5 | 68.80ms | 33004.66ms |
| 6 | 70.11ms | 31606.83ms |
ext4は65.22msから74.92msに収まった。一方の/mnt/cは27170.14msから45175.41msまで動き、最遅は最速の1.66倍。正直、差の大きさよりもこの揺れのほうが扱いづらい。所要を予測しにくく、短い処理でも待たされる回が混ざる。
git status は180倍
合成テストだけでは配置先を決める材料として弱いので、同じ1000ファイルを追跡するGitリポジトリも両側に作った。1ファイルだけ書き換え、WSL側の Git 2.43.0 で git status --porcelain=v1 --untracked-files=all を6回ずつ実行した。毎回 files/d0000/f00000.bin の変更1件だけが返ることを照合している。
| 最速 | 中央値 | 最遅 | |
|---|---|---|---|
| VHDX内ext4 | 4.45ms | 5.28ms | 7.15ms |
/mnt/c 9p |
744.58ms | 950.97ms | 1658.78ms |
中央値では180.11倍だった。1000ファイルなら/mnt/cでも約1秒なので使えないわけではない。ただ、保存のたびにIDEやフックが git status を呼ぶ環境では、この約946ms差が何度も積み上がる。手元では、WSLから主に触るリポジトリをext4側へ置く理由として十分な数字である。
最初は5000ファイルで始めて失敗した
当初の設定は5000ファイル、20MB。ext4の1ラウンド目は760.89msだったのに、/mnt/cは415538.27ms、約6分56秒まで伸びた。6ラウンドを終えるまで20分以上待ち、その後のGit確認に到達したところで停止。
原因は性能ではなく、私の検証コードにある。git status --porcelain の先頭空白を strip() で消してから、空白込みの " M " と比較するコードだった。実際の出力は正しいのに、検査側だけが失敗扱いになってしまう。生のJSONを書き出す前に落ちたため、最初の5000ファイル測定は正式な集計から除外している。
同じ失敗を繰り返さないよう、出力のパスと状態文字は別々に確認する形へ変更。ファイル計測が終わった時点で途中JSONも保存するようにした。再実行時間が現実的になるよう、既定値は1000ファイルへ下げている。5000ファイルの415.5秒は参考値として残るが、成功した6ラウンド表からは除外したままだ。
fsyncしていないので、耐久時間ではない
書込みはPythonの write_bytes() で閉じたところまでを測り、fsync は呼んでいない。そのため、表の作成時間はディスクへ永続化し終えるまでの時間ではない。作成後のwritebackが、続く読込やstatへ重なった可能性も残る。
ここでは4工程を分離したストレージ性能より、作成から削除までの合計を主指標にした。普段のGitやnpmが小ファイルを扱うときの待ち方へ寄せたかったためだ。直後の読込はcold readではなく、明示的にhot readと呼んでいる。
Windows Defenderは測定中も有効で、リアルタイム保護もTrueだった。除外パスは1件あったが、このリポジトリは対象外。Defenderを止めた比較はしていないので、/mnt/cの遅さのうち何割が9p、NTFS、Defenderのどれに由来するかは分けられなかった。
ARM64固有の差とは断定しない
環境は Surface Pro 11th Edition、Snapdragon X Elite X1E80100、Windows 11 Pro 10.0.26200 ARM64。WSLは2.7.3.0、カーネルは6.6.114.1-microsoft-standard-WSL2、uname -m はaarch64だった。Linux側からは12コアが見えている。
ただし、x64のWindows機や別のWSLバージョンでは測っていない。今回の456.75倍をSnapdragon X Elite固有の性質だとは言えない。観測できたのは、このARM64実機でVHDX内ext4と9p越しのCドライブに同じ処理を流した差だけだ。
ほかにも未検証がある。実在する大規模リポジトリのcloneやnpm install、並列ビルド、ディレクトリ列挙は外した。Windows側のツールとWSL側のツールが同じファイルを交互に触る運用も試していない。ext4へ置くとWindowsアプリからの扱いが不便になる場面があるため、速度だけで全プロジェクトを移す話でもない。
私の置き方は変えた
WSLでGit、Node.js、Pythonを回すリポジトリは、VHDX内のホームディレクトリへ置く。Windows側から参照したいときは \\wsl$\Ubuntu\home\... を使う。逆に、WindowsネイティブのIDEやスクリプトが主役ならCドライブのままにする。
予想と違ったのは、「/mnt/cは少し遅い」では済まなかった点だ。1000ファイルの合計で32.3秒対0.071秒、git statusで0.951秒対0.005秒。少なくとも2026年8月9日のこの機械では、WSL中心の開発を/mnt/cへ置くと、CPU性能より先にファイル境界で待つ。測った範囲では、配置先を変える効果がいちばん大きかった。