PowerShell 7 の起動が Windows PowerShell 5.1 より遅かった話(どちらも ARM64 ネイティブ)
pwsh -NoProfile -c exit の中央値は386.8ms、powershell -NoProfile -c exit は212.2ms だった(起動だけを見ると、思ったより差が大きい)。
PowerShell 7 のほうが新しいのだから、少なくとも起動だけなら同じくらいだろうと思っていた。2026年8月2日に Surface Pro 11th Edition で測ると、結果は逆になる。Snapdragon X Elite X1E80100、Windows 11 Pro 10.0.26200 の ARM64 版、AC 接続、電源プランは「バランス」(GUID 381b4222-f694-41f0-9685-ff5bb260df2e)という、普段の作業に近い条件。
最初に疑ったのはエミュレーションだった。けれど PE ヘッダーを読むと、pwsh も powershell.exe も Machine が 0xAA64。どちらも ARM64 ネイティブで、片方だけ x64 だから遅い、という逃げ道はなかった(ここで外していたら記事の方向がかなり変わっていたと思う)。
7回だけ起動して比べた
Measure-Command で7回まわし、初回と2回目以降を分けて記録した。ファイルキャッシュやランタイムの状態で初回は揺れやすいので、全部をひとつの中央値にしてしまうと読みにくい(この手の起動時間は、最初の1回だけ妙に重くなることがある)。
1..7 | ForEach-Object {
(Measure-Command { pwsh -NoProfile -c "exit" }).TotalMilliseconds
}
-NoProfile は外せない条件にした。自分の pwsh プロファイルには補完や見た目まわりの設定が入っているので、それを読ませると「PowerShell 7 が遅い」のか「自分のプロファイルが重い」のか分からなくなる。OneDrive の同期が落ち着いてから始めたが、常駐を全部消したクリーンな測定ではなかった。
数字はかなり揺れる
初回を含めた7回分を、そのまま表に残しておく。
| コマンド | 初回 | 2回目以降の中央値 | 全7回 |
|---|---|---|---|
| pwsh -NoProfile -c exit | 535.3ms | 386.8ms | 535.3 / 361.2 / 563.7 / 454.2 / 324.5 / 300.4 / 386.8 |
| powershell -NoProfile -c exit | 284.6ms | 212.2ms | 284.6 / 183.6 / 185.4 / 263.8 / 211.9 / 212.2 / 217.3 |
| python -c pass | 64.3ms | 58.0ms | 64.3 / 38.7 / 58.0 / 42.4 / 40.9 / 90.2 / 131.9 |
| node -e 0 | 110.8ms | 95.9ms | 110.8 / 135.8 / 80.3 / 84.4 / 94.4 / 100.5 / 95.9 |
| git --version | 106.9ms | 91.6ms | 106.9 / 80.4 / 97.8 / 58.9 / 91.6 / 104.3 / 71.9 |
pwsh は300.4ms から563.7ms まで振れている。最速と最遅で1.9倍あるので、1回だけ測って「こっちが速い」と決めるのは危ない。powershell.exe も183.6ms から284.6ms まで動くが、それでも中央値では212.2msに収まった(この揺れを見て、7回でも多すぎるとは感じなくなった)。
ついでに測った python、node、git は、起動だけならかなり軽い。python は58.0ms、node は95.9ms、git は91.6ms。普段「PowerShell のワンライナーが少し重い」と感じていたのは、勘違いだけではなかったらしい(同じ短いコマンドでも、入口の重さがかなり違う)。
中央値の取り方でやらかした
今回いちばん危なかったのは、最初の測定スクリプトをそのまま信じかけたことだった。3回測って中央値を取るつもりで、こう書いていた。
$sorted = $ms | Sort-Object
$median = $sorted[[int]($n / 2)]
$n が3のとき、3 / 2 は1.5になる。PowerShell の [int] は銀行家丸めなので、1.5 は2へ丸められる。3要素の配列で添字2は最大値。つまり中央値ではなく、こっそり最悪値を拾っていた。
出てきた数字が体感より重く見えて、そこでやっと気づくことになった。[math]::Floor() に直し、回数も7回へ増やして測り直したのが上の表。エラーも警告も出ないので、この手のバグはかなり嫌らしい(答えの形だけはそれっぽく見える)。
遅い理由をARM64には押しつけない
pwsh は .NET のランタイムを立ち上げてから動く。手元の .NET SDK は 9.0.316 で、dotnet --info の RID は win-arm64。ランタイム側もネイティブなので、「ARM64 だから遅い」という話にはしにくい。
むしろ PowerShell 7 の起動構造そのものの差、と見るほうが自然だった。x64 機でも pwsh の起動が Windows PowerShell 5.1 より重いという話は珍しくないし、今回の386.8ms対212.2msも、その延長に見える。とはいえ、ARM64 Windows 上で実際にこの差が出たことは、短い自動化では無視しにくい。
1日1回だけ走るスクリプトなら、この差はほとんど気にならない。けれどタスクスケジューラや小さな補助コマンドで1日100回呼ぶと、17秒ぶん余計に待つ計算になる。ここまで積むと、気持ちの問題だけでは済まなくなってしまった。短い処理ほど本体より起動の割合が大きくなるので、シェルを選ぶだけで妙に効いてくる場面がある。逆に、長い処理ならこの差はすぐ埋もれるので、起動時間だけを見て全部を powershell.exe に寄せるのもまた違うし、対話で使う場所、タスクスケジューラから呼ぶ場所、他のツールが内部で呼ぶ場所を分けて考えないと判断ミスになった。差は残った。
対話と自動化で分ける
対話的に使うシェルは、今も pwsh のままにしている。補完、履歴、表示の感じが違うので、毎日触る場所を Windows PowerShell 5.1 に戻す気にはならなかった。起動時に386.8ms払っても、開きっぱなしならその後はほとんど気にならず、実際の対話操作では待たされる感じは薄い。対話で使う場所、タスクスケジューラから呼ぶ場所、他のツールが内部で呼ぶ場所を分けて考えないと判断ミスになった。
一方で、短いスクリプトを大量に呼ぶ用途は powershell.exe に寄せた。書いている処理が 5.1 の範囲で足りるなら、起動が半分近くで済むほうを選ぶ。2026年7月末に自動化を組み直したとき、pwsh と powershell.exe が混ざっていた箇所を、この基準で整理した。呼び出し元がタスクスケジューラなのか、別の CLI からの補助処理なのか、手元で開きっぱなしにする対話シェルなのかで、同じ386.8msでも意味が変わる。用途を分ける。入口だけ見た。測って決める。
補足すると、この差はあくまで起動時間だけの話。ループを回す、ファイルを舐める、API を叩く、といった処理本体で pwsh が遅いと感じた場面は、少なくとも手元の作業ではまだ出ていない。測る対象を「何もしないで exit する」ところに絞ったから見えた差で、日常の作業全体を212.2msと386.8msだけで語るのは無理がある。
測り直してよかった、というのが最後に残った感想。なぜ最初の表を疑えたのかといえば、体感より少し重すぎる数字に見えて、そこで一度立ち止まれたからだった(この小さな違和感がなければ、そのまま出していた可能性がある)。最初の中央値バグをそのままにしていたら、もっと大げさな差として書いていたはずで、数字がそれっぽいときほど疑う必要がある。最初はどちらを採用するか迷ったが、測定の失敗を潰してから判断するほうを選び、同じ測り方をもう一度試してみた。