◆ WSL から Windows のファイルシステムを見るのは遅い
◆ PHP だとリクエストごとにファイルを読み取るので特に遅い
◆ ファイルは Windows 側に置いて管理したい
◆ WSL 側にはコピーを配置して Windows 側での変更を監視して変更を反映

Windows 側にファイルがあると重い

WSL2 では Windows 側のファイルシステムからファイルを読み取るのが遅めです
ちょっとしたものだと気にするほどでもないのですが ファイル数が多くなるとつらめです

Node.js だとプロセス起動時に全部ロードしてあとはメモリ上にあります
なので リクエストの処理をするときに Windows 側のファイルを読み取る必要はありません
最初の起動は遅いですが 以降は高速です
ちょっとコードを書き換えて再起動を頻繁に繰り返すでもなければあまり困りませんでした

しかし PHP ではそうはいきませんでした
仕組み上 毎リクエストごとに PHP ファイルを読み取るのでフレームワークを使っていたりして読み取るファイルが多くなると全部のリクエストが遅いです
ページを開くと複数の API を呼び出すようなページでは表示されるまですごく時間がかかります

そういうこともあって WSL では WSL 側のファイルシステムにファイルを置くことを推奨していますが 個人的にそれはしたくないです
一時的な使い捨てなら全然かまわないですが ちゃんと保存して管理するものは Windows 側でまとめて管理したいです
そもそも WSL 自体が Docker コンテナみたいな使い捨てなところって扱いなので重要なものを起きたくないんですよね
毎回 1 から作るのは面倒なので基本は残しておいて使い続けるけど重くなってきたりすると削除して作り直すものです
そういうところに重要なファイルを置いておくとバックアップ漏れで消えたりどこにいったかわからなくなったりします

でもここまで遅いのはさすがにどうにかしたいです

自動コピー

Windows 側のデータを自動でミラーするような仕組みにすることにします
ファイル本体は Windows にあって編集するのも Windows 上です

最初に WSL に tmpfs のフォルダを用意してそこに全コピーします
後は編集するときには変更を監視して変更があったファイルを WSL 側に自動でコピーします

これまでリクエストごとに Windows からファイルを読み取っていたのが 変更があったときにそのファイルを転送するだけで済むのでムダがないです
WSL 側のコピーは一時的なものの扱いなので tmpfs にして再起動とかで消えてもいいもの扱いです
WSL の tmpfs は遅いみたいな記載も見かけましたが read/write が 数 GB/s 程度には速かったのでたぶん WSL1 の記載なんでしょう

速度

重要なのは PHP のページの表示速度なので 計測してみることにしました
とりあえず重そうなものということで laravel のチュートリアルにあるプロジェクトを使います

composer create-project laravel/laravel example-app
cd example-app
php artisan serve

これで起動したサーバのトップページ (/) の速度を測ります
API ではなくページ自体が PHP で処理されるものなので devtools で HTML のリクエストの時間を見ます

Windows のフォルダを見に行く場合はだいたい
1.6 ~ 1.8s 程度で遅いときは 2 ~ 5s くらいでした
結構幅があります

tmpfs に移すと数十 ms になりました
ただ stalled や initial connection で時間が取られて 300ms 程度かかるときもあります
それでも十分に速いです

監視してコピー

監視して自動で同期してくれるツールがあってもよさそうなのにこれといったものは見つからなかったです
PowerShell の標準機能とかであっても良さそうなのに

専用ソフトみたいなのは入れたくなかったので PowerShell のスクリプトを作りました

$global:src = "C:\wsl\php\example"
$global:dst = "\\wsl$\Ubuntu\opt\tmpfsdir"

$watcher = New-Object IO.FileSystemWatcher $src
$watcher.IncludeSubdirectories = $true

$onChange = {
$name = $Event.SourceEventArgs.Name
Write-Host ("copy: " + $name)
Copy-Item (Join-Path $src $name) (Join-Path $dst $name)
}

Register-ObjectEvent $watcher -EventName "Changed" -Action $onChange

src と dst を global にしてますが global にしないと onChange ブロックが呼び出されるときに参照できませんでした
タイマーとかのイベントだとスクリプトファイルのトップレベルにある変数は見れるのですけどね

とりあえず用意したものなので 更新イベントしかハンドルしてないです
ちゃんと動かすなら作成や削除や移動にもハンドラ設定が必要です

対応しようかと考えていたときに robocopy に監視機能があるのを見つけました
/MON:1 をつけると監視して 1 を指定したので 1 つ以上変更があると再コピーしてくれるようです
ならそっちでいいやと試してたら チェックはリアルタイムではなく 1 分間隔でした
間隔の設定もできるのですが 単位が分なので 1 分が最小間隔です

方法はないのか探すと 監視なしの robocopy を実行ごとに数秒 sleep してから再実行を繰り返せばいいという書き込みもありました
それだと全ファイルの再チェックを数秒置きにやることになるのでディスクアクセスが多くなりすぎる気がします
他の全体検索とかが遅くなったり SSD 寿命とかの面で不安もあるので やっぱり監視する仕組みを使いたいです
ただファイル・フォルダの作成・削除をイベントごとにそれぞれ処理するのは面倒なので 何かの変更があれば robocopy でミラーリングするのでいいかなと思ってます