PowerShellのプロファイル (Microsoft.PowerShell_profile.ps1) に多くの関数や設定を記述すると、単一のスクリプトに大量にコードを書くことになり、管理が複雑になる。 このために複数ファイルに機能を分割し、論理分割を行うことで管理を簡単にする方法があるが、PowerShellにおいて他ファイルスクリプトの実行は大きな処理であるため、起動が遅くなる問題が知られている この問題に対処するため、スクリプトを「即時読み込み」と「遅延読み込み」に分割する仕組みを導入した。
これにより、30スクリプト程度に分割された状態で、591ms の起動時間を実現した。
結論
~/Documents/PowerShell/Microsoft.PowerShell_profile.ps1 に以下のスクリプトを記載する。
$psdir="$ENV:HOMEDRIVE$ENV:HOMEPATH\.config\PowerShell"
$autoloadPath = "$psdir\autoload"
$lazyloadPath = "$psdir\lazyload"
Write-Host ("Load PS Profiles from {0}" -f $autoloadPath) -ForegroundColor DarkCyan
$measure = Measure-Command {
Get-ChildItem $autoloadPath
| where Extension -eq ".ps1"
| where-object {$_.Name -notmatch "^~"}
| ForEach-Object {
$m = Measure-Command {
. $_.FullName
}
Write-Host ("Done: $($_.FullName) in {0:N3} seconds." -f $m.TotalSeconds) -ForegroundColor DarkCyan
}
$m = Measure-Command {
if (Test-Path $lazyloadPath) {
Get-ChildItem -Path $lazyloadPath -Filter "*.ps1" | ForEach-Object {
Write-Host "Lazy: '$lazyloadPath\$($_.Name)'" -ForegroundColor DarkCyan
$command = $_.BaseName
$orig = $_.FullName
$hook= @"
Write-Host "Lazy loading '$command' " -ForegroundColor Yellow
Set-Item -Path "Function::global:$command" -Value ([scriptblock]::Create("& ``"$((Get-Command $command -CommandType Application -ErrorAction Stop).Source)``" @args")) -Force
. "$orig"
& "$command" @args
"@
New-Item -Path "Function::global:$command" -Value ([scriptblock]::Create($hook)) -Force | Out-Null
}
}
}
Write-Host ("Done: lazy in {0:N3} seconds." -f $m.TotalSeconds) -ForegroundColor DarkCyan
}
Write-Host ("Load completed in {0:N3} seconds." -f $measure.TotalSeconds) -ForegroundColor DarkGreen
個人的なおすすめは、 ~/.config/PowerShell/Microsoft.PowerShell_profile.ps1 に保管することだ。 ~/Documents/PowerShell/Microsoft.PowerShell_profile.ps1 へリンクを張ることで管理が楽になる。
続けて、既存のコードや機能があれば分割する。
分割したコードは ~/.config/PowerShell/autoload/ および ~/.config/PowerShell/lazyload に ps1 スクリプトを配置する。
この際、 lazyloadフォルダには「遅延展開する際の名前」と同一にする必要がある。例えば、 Python のパッケージマネージャ rye を管理する場合は rye.ps1として保管すると都合がよいだろう。
解説
このスクリプトは、2種類のディレクトリパス($autoloadPath と $lazyloadPath)を組み合わせて、スクリプトの読み込み方を制御している。
autoload
こちらは極めて単純な仕組みになっている。
$autoloadPathで指定されたディレクトリ(~/.config/PowerShell/autoload)内のファイルを列挙する。.ps1ファイルにフィルターをかけ、実行する。- ファイル名が
~から始まれば無効化とし、実行を取りやめる。
プロンプト設定や頻繁に使うエイリアスなど、起動時に必須のスクリプトをここに配置することで、一切に登録なしに自動実行が可能となる。 PSReadLineのカスタムコマンドなどはここに該当するだろう。
lazyload
$lazyloadPathで指定されたディレクトリ(~/.config/PowerShell/lazyload)内のファイルを列挙する。- これらのファイルを実行する代わりに、スクリプトのファイル名と同名のフックがグローバル空間に作成される。この処理はファイル読み込みより遥かに軽量である。
- ユーザーがそのコマンドを初めて実行すると、フック関数がトリガーされる。
- フック関数は、本来のスクリプトファイルを読み込んで自身を上書きする。その後、引数を引き継いで本来の処理を実行する。
- 2回目以降の実行ではフック関数は破棄され、既に本来の関数に置き換わっているため、直接その関数が実行される。
これにより、使用頻度の低い関数の読み込みを、実際に必要になるまで遅延させることができる。