ARM64_Lab

WSL2 の /mnt/c に1000ファイル置いたら、ext4 の457倍かかった

この記事の見出し
  1. 比べたのは裸のSSD同士ではない
  2. 1000個を6ラウンド
  3. 中央値は70.73ms対32305.75ms
  4. git status は180倍
  5. 最初は5000ファイルで始めて失敗した
  6. fsyncしていないので、耐久時間ではない
  7. ARM64固有の差とは断定しない
  8. 私の置き方は変えた

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-benchfindmnt では /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=drvfsmsize=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性能より先にファイル境界で待つ。測った範囲では、配置先を変える効果がいちばん大きかった。

a
arm64lab — 個人運営

Surface Pro 11th Edition(Snapdragon X Elite)を2025年5月から常用機にしている個人の記録です。ARM64 版 Windows で詰まったところと、その場で測った値をそのまま書き残しています。特定の企業・団体とは関係がなく、いかなる組織を代表する見解でもありません。