MJHD

エモさ駆動開発

WSLのIO遅いよ問題

WSLは理論的にはLinuxカーネルを起動するオーバーヘッドがなく軽量に動作するはずなのだが、実際に動かしてみると色々ともっさりしている。
例えばnpm installなどが重たすぎて永遠に終わらなかったりエラーが出てしまったり。

この辺のもっさりは、WSLのIOが重いことに起因する。
www.phoronix.com

↓のスレッドではWSLのIOはなぜ遅いのか問題が議論されている。
github.com

SvenGrootさんの説明1

LinuxのIO操作とWindowsのIO操作の設計が根本的に違うことが原因。解決しようとしたら、Linuxの挙動を完璧に再現しなければならない。(例えば、Git for WindowsWindowsへポートされたプログラムだが、Windows用に挙動を変更することで対処している)
IO操作のパフォーマンスをあげる努力はしているが、WSLチーム(LxFSやDrvFSなどのVFS周り)、NTFSチーム、NTFSのフィルタ*1を実装する各ベンダ(Windows Defenderやサードパーティのウィルス対策ソフト)などの沢山の関係者がいて、複雑でとても進みの遅い作業になる。
例えば、Creator's Updateではstatコールのパフォーマンスを向上させる新しいファイルシステムAPIを導入した。そして、この変更を知らせ、使用してもらうために内外のベンダに働きかけている。
TL;DR: ファイルシステムのパフォーマンス問題は難しい、けど改善を続けてくよ。苦しみはわかるけど問題を無視してるわけじゃないよ。

注: 大まかな訳
https://github.com/Microsoft/WSL/issues/873#issuecomment-391810696

*1: ファイルのIOにサードパーティが独自の処理を挟めるもの。Windows Defenderはこれを使ってファイル変更の監視をしている

SvenGrootさんの説明2

ここまでの問題は簡単に言えば、"NTFSは遅い"か"DrvFSは遅い"だと思う。
IOサブシステムはWindowsLinuxでかなり違う設計になっている。大きな違いは以下の3点。

  1. Linuxはトップ階層にキャッシュを持っていて、キャッシュに存在するエントリに関してはFS内部を探索しなくても返すことができる。 Windowsにはこのようなキャッシュがなく、ファイルシステムに大きく頼っている。Win32の「C:\dir\file」のようなパスは「\??\C:\dir\file」というNTパスに変換され、「\??\C:」部分はObjectManagerが管理する\Device\HarddiskVolume4などへのデバイスオブジェクトへのシンボリックリンクを表している。
    一度このようなデバイスオブジェクトを発見すると、Windowsは残りのパスを単純にファイルシステムへ託す。ここがLinuxVFSが行うような中央集権的なパス解析との違いである。

  2. WindowsのIOスタックは拡張性を重視しており、ファイルシステムへのIO操作要求を実行する前にフィルタドライバを設定することができるようになっている。これは、例えばウィルススキャン、圧縮、暗号化、OneDriveのような仮想的なファイル、アプリの起動高速化のためのpre-fetchingなどなど至る所で使われている。Windowsクリーンインストールしたとしても、すでにWindowsはある程度の数のフィルターが特にシステムドライブ(C:)に関しては動いている。(なのでD:ドライブや他のパーティションが存在するマシンなら、よりフィルタが少ない傾向にあるシステムドライブ以外を使うことを推奨する)
    これらのフィルタは沢山のIO操作、特にファイルのオープンと作成に設定されている。

  3. NTのファイルシステムAPIはパスベースではなく、ハンドルベースで設計されている。なので、ほとんどいかなる操作もはじめにファイルを開きハンドルを取得する必要があり、この操作はパフォーマンスに影響する。例えば、Win32 APIでファイル削除の1コール(DeleteFileなど)を実行したとしても、実際にはOpen/Close操作が走っている。少し前にリリースしたDrvFSの大きな改善は、ファイルの情報取得のためにファイルを開かなくても良い新しいAPIを用意することだった。

基本的にファイル操作はLinuxよりもWindowsの方が重い。とりわけファイルのメタ情報に触れる操作は。
これらの重さの原因はObjectManager*1やIO Manager*2、フィルタ、そしてNTFSに広がっている。
単純に「NTFSが遅い」というだけならNTFSの最適化の修正を行えば良いが、問題は広大なコンポーネントに広がっており、銀の弾丸がない状況である。
また、内製のフィルタだけならまだいいが、サードパーティのフィルタが内部で何を行なっているかは知るすべがないので、各ベンダと共にこれらの問題の改善を試している。例えば、ファイル情報を取得する新しいAPIを導入し、フィルタドライバもそのAPIをサポートした時、まだ新しいAPIに対応していないフィルタがインストールされていたとしても正常に動作することを保証しなければならなかった(基本的にはOpen/Query/Close処理へフォールバックする)。その他もこれをサポートしていることを確認し、最大のパフォーマンスの恩恵を受けられるようにするためには沢山の時間と労力がかかった。
同じことが大文字小文字を区別するディレクトリなどにも言え、変更後の動作をフィルタのエコシステムが正常にハンドルできるのか確かめなければならない。

...中略...

また、単純に「Windowsが遅さの原因」と言うわけではなく、WSLの影響もある。特に、WindowsLinuxのパス解析の挙動の大きな違い(Linuxは中央集権、上でも述べた通りWindowsファイルシステムに任せている)がある。LinuxアプリはLinuxの挙動を想定して作られている。なので我々も慎重にその挙動をエミュレートし、不幸なことにも沢山のIO操作をファイルシステムに送ることになってしまっている。例えばstatコールも、Linuxはキャッシュ*3にヒットさえすればLinuxカーネルが全体を応答するが、WSLではフィルタ全体を走査する複数のリクエストをファイルシステムに対して送らなければならない。
我々はWSLで余計な処理が必要にならないよう沢山の作業を間も無くリリースされる1809で行なった。
だけど、LinuxWindows APIが違う以上、ここにはどうしても余計な処理が必要になってしまう。

基本的には、これは単純な問題ではなく、沢山のコンポーネントが関わっていて、フィルタ機能に変更を加えるならMicrosoft's Partnersと共に取り組んでいかなければならない問題である。
かといって諦めるわけではないよ〜。

注: 大まかな訳
https://github.com/Microsoft/WSL/issues/873#issuecomment-425272829

*1 ObjectManager: オブジェクト(Windowsにおけるリソースの単位)を管理するコンポーネント
*2 IO Manager: 外部入出力を管理するコンポーネント。IRP(上で言うファイル操作のリクエスト)を送信する先
*3 dentry cache: ファイル情報を格納するdentryをメモリ上にマップでキャッシュしたもの

まとめ

  • WindowsのIO操作はLinuxより高価
    • フィルタ機能という拡張性を持たせている
    • Linuxカーネルのように中央集権のcacheを持たず、パスの解析からFSに任せている
  • パフォーマンス改善の取り組みは、社内複数チーム、サードパーティと登場人物が多く、労力と時間がかかる
  • Linuxを前提に書いたIO中心のプログラムはWSLで死ぬかも

There's a reason we have made it clear from its initial release, that WSL is not built for hosting production services/workloads ;)

WSLはプロダクションサービスやワークロードをホストするために作られたんじゃないよとリリース当初から明言している