前回はWindowsでLinuxのコマンドを実行する方法として「WSL2」と「MSYS2」の違いについて、特にメモリの使用量の違いという観点から説明を行った。MSYS2はWindowsでネイティブに動作するLinuxコマンド群であり、WSL2はHyper-V仮想環境で動作するLinux環境でのコマンドだ。抜本的に使用している技術が異なっており、動作の特徴がかなり異なる。

WSL2はLinuxカーネルの動きがそのまま反映されるため、大規模ファイルなどを扱う処理でメモリの消費が激しい。知らずに使っていると、WSL2の使用後になぜかホスト側のWindowsでメモリ不足が発生するという事態が起こりかねない。「wsl —shutdown」でWSL2を終了すれば解消されるのだが、知らなければシステムを再起動しないと解消されないことになり、なかなかに厄介だ。

今回は実行速度の面からWSL2とMSYS2を比較していく。

実行速度がめっちゃ遅いMSYS2

まず、次のようなベンチマークスクリプト「bench.sh」を作成する。これは、「dateコマンドを100回実行する」というもので、通常のLinuxであればどうということのない処理負荷だ。

#!/bin/sh

i=0
l=100
cmd=date

while [ $i -lt $l ]
do
    $cmd > /dev/null 2>&1
    i=$(($i + 1))
done

このスクリプトをMSYS2とWSL2で実行し、処理時間を計測すると次のようになる。

◆MSYS2での実行時間

PS C:\Users\daichi> time -p ./bench.sh
real 14.13
user 3.80
sys 9.74
PS C:\Users\daichi>

◆WSL2での実行時間

daichi@XPS-13-9305:/mnt/c/Users/daichi$ time -p ./bench.sh
real 0.04
user 0.03
sys 0.01
daichi@XPS-13-9305:/mnt/c/Users/daichi$

処理時間を比較すると次のようになる。

実行時間[秒] 速度比率
MSYS2 14.13
WSL2 0.04 353倍

ベンチマーク実行時間比較

MSYS2での実行速度がものすごく遅いことがおわかりいただけるだろう。MSYS2はネイティブバイナリが動作しているのだが、とても遅い。一方、WSL2は仮想環境で動作しているが、非常に高速だ。

MSYS2のコマンドはなぜ遅いのか?

MSYS2の実装に関しては筆者も深く追っていないのでざっくりとした説明になるが、MSYS2のコマンドがリンクしているmsys-2.0.dllを介した処理に時間がかかっているように見える。

◆C:\msys64\usr\bin\date.exeがリンクしているファイル

PS C:\Users\daichi> ldd C:\msys64\usr\bin\date.exe
        ntdll.dll => /c/WINDOWS/SYSTEM32/ntdll.dll (0x7fffc2fe0000)
        KERNEL32.DLL => /c/WINDOWS/System32/KERNEL32.DLL (0x7fffc1a80000)
        KERNELBASE.dll => /c/WINDOWS/System32/KERNELBASE.dll (0x7fffc0a30000)
        msys-2.0.dll => /usr/bin/msys-2.0.dll (0x180040000)
        msys-intl-8.dll => /usr/bin/msys-intl-8.dll (0x430b30000)
        msys-iconv-2.dll => /usr/bin/msys-iconv-2.dll (0x5603f0000)
PS C:\Users\daichi>

MSYS2のコマンドはWindowsネイティブなバイナリだが、Linuxのコードをそのままコンパイルして実行できるように1枚レイヤのようなものを噛ませている。この部分で関数の差異を吸収し、Windowsが提供している機能に置き換えて処理を実行しているのだが、どうもこの部分ですごく重いパスがあるように見える。

MSYS2でもC:\msys64\usr\bin\ではなくC:\msys64\mingw64\bin\といったディレクトリ以下のバイナリはmsys-2.0.dllはリンクしていない。こちらは基本的にWindowsのライブラリのみをリンクする仕組みになっている。

PS C:\Users\daichi> ldd C:\msys64\mingw64\bin\python3.9.exe
        ntdll.dll => /c/WINDOWS/SYSTEM32/ntdll.dll (0x7fffc2fe0000)
        KERNEL32.DLL => /c/WINDOWS/System32/KERNEL32.DLL (0x7fffc1a80000)
        KERNELBASE.dll => /c/WINDOWS/System32/KERNELBASE.dll (0x7fffc0a30000)
        msvcrt.dll => /c/WINDOWS/System32/msvcrt.dll (0x7fffc18e0000)
        libpython3.9.dll => /mingw64/bin/libpython3.9.dll (0x7fff41fa0000)
        ADVAPI32.dll => /c/WINDOWS/System32/ADVAPI32.dll (0x7fffc0fb0000)
        sechost.dll => /c/WINDOWS/System32/sechost.dll (0x7fffc1ba0000)
        RPCRT4.dll => /c/WINDOWS/System32/RPCRT4.dll (0x7fffc1da0000)
        SHLWAPI.dll => /c/WINDOWS/System32/SHLWAPI.dll (0x7fffc1c40000)
        WS2_32.dll => /c/WINDOWS/System32/WS2_32.dll (0x7fffc2f30000)
        VERSION.dll => /c/WINDOWS/SYSTEM32/VERSION.dll (0x7fffb5a20000)
PS C:\Users\daichi>

C:\msys64\mingw64\bin\以下のPython 3.9を使ったすごく簡単な次のようなスクリプト「hello.py」を用意する。

#!env python3.9

print("Hello World!")

このスクリプトを使うように先程のベンチマークスクリプトを書き換える(bench2.sh)。

#!/bin/sh

i=0
l=100
cmd="python3.9 ./hello.py"

while [ $i -lt $l ]
do
    $cmd > /dev/null 2>&1
    i=$(($i + 1))
done

このスクリプトをMSYS2とWSL2で実行すると次のようになる。

◆MSYS2での実行時間

PS C:\Users\daichi> time -p .\bench2.sh
real 6.28
user 0.25
sys 0.46
PS C:\Users\daichi>

◆WSL2での実行時間

daichi@XPS-13-9305:/mnt/c/Users/daichi$ time -p ./bench2.sh
real 2.55
user 1.56
sys 0.33
daichi@XPS-13-9305:/mnt/c/Users/daichi$

実行速度を比較すると次のようになる。

実行時間[秒] 速度比率
MSYS2 6.28
WSL2 2.55 2.46倍

こちらは2.5倍ほどの開きしかない。MSYS2のほうも結構高速に動作している。どうもmsys-2.0.dllが実行速度のボトルネックになっているように思われる。

C:\msys64\usr\bin\とC:\msys64\mingw64\bin\

MSYS2のC:\msys64\usr\bin\には、もともとLinuxで実行できるコマンドがそのまま収められている。一方、C:\msys64\mingw64\bin\といったディレクトリにはWindowsに移植されたコマンドが配置されているほか、Windowsへ移植したいソフトウエアを開発する場合に利用できる環境となっている。目的からしてそもそも違うので、実行速度にこうした差が出るのも当然ではある。

C:\msys64\mingw64\bin\側のほうに使いたいコマンドがあるならラッキーだ。コマンドはそこそこの速度で動作してくれる。一方、そうでない場合にはC:\msys64\usr\bin\にインストールされるコマンドを使う必要がある。実行速度が遅いという問題はあるものの、Windowsでネイティブに動作し、Windowsの環境と親和性は高い。

どこを優先するかという話になるが、実行速度を重視するならWSL2、Windowsとの親和性を求めるならMSYS2ということになる。最初に示したスクリプトがMSYS2では350倍ほど実行速度が遅いわけだが、ほかにもこれまで使っていたスクリプトをMSYS2で実行したらびっくりするくらい遅くなった例というのはたくさんある。場合によっては使い物にならないはずだ。C:\msys64\usr\bin\ではなくC:\msys64\mingw64\bin\側のコマンドを使うことである程度緩和することはできるが、それでもかなり遅い。

WSL2をMSYS2のようにWindowsでネイティブに動作しているように「見せかける」ことはできる。ただし、いくつかの処理はうまく機能しないので、MSYS2のような親和性は期待できない。どっちつかずな状況なのだが、優先順位を整理することでどちらを採用すべきかは見えてくると思う。自分の求めるものに合わせて採用する技術を選んでいこう。