2024-05-19

F# でコマンドレットを書いてる pt.40

krymtkts/pocof の開発をした。

はじめメモリ浪費に気付いたときの使い方は pocof の起動までにめちゃくちゃ時間がかかる。

    function global:Show-ReadLineHistory() {
Get-Content -Path (Get-PSReadLineOption).HistorySavePath | Select-Object -Unique | Select-Pocof -CaseSensitive -Layout TopDown
}
Set-Alias pghy Show-ReadLineHistory -Option ReadOnly -Force -Scope Global

こういう使い方をしてるのだけど、 Select-Object -Unique がめちゃくちゃ遅い。 どういう実装をしてるかまでおってないが、順不同の要素中の他の重複を取り除くから遅いっぽい。 ここを Get-Unique にしたら爆発的に速くなるのだけど、まず Sort-Object で並び替えておく必要があり、それは期待した動作じゃない。元の順そのままで重複だけなくしたい。

そこまできたらもう面倒なので pocof に -Unique オプションつけた方が楽やろなということで、そういう機能を実装した。 #181

PowerShell で言うところのこういうコードにしたら、とても速く重複が取り除ける。

Get-Content -Path (Get-PSReadLineOption).HistorySavePath | % -begin {$a=[ordered]@{}} -process {if ($a.Contains($_)) {} else {$a.Add($_, $null)}} -end {$a.Keys}

[ordered] は pocof のコードだと System.Collections.Specialized.OrderedDictionary になる。 pocof のInputObject の並び順を維持しつつとなるとこいつを使うのが定石だろう。

System.Collections.Specialized.OrderedDictionary では key の等価性を判定するのに EqualsGetHashCode が使われる。 なので PSCustomObject の場合だと重複が取り除けない。 PSCustomObjectObject.Equals の再定義とかしてないから参照等価性しかチェックしてないみたい。 ちょっと残念だが、でも PowerShell の環境では大体の object はそれらを実装してるし、いったん PSCustomObject 用の特別な比較関数を書かないでやった。

-Unique オプション対応してみてわかったのだけど、 hashtable の key-value の組み合わせが一致するエントリも除外できるし、こりゃなかなか便利な機能な気がしている。ぱぱっと思いついて作った割には。

pocof の設計思想的に、 pocof の呼び出し前後で他の Cmdlet でできることはあまり実装しないことにしてる。 それが PowerShell らしいかなと(勝手に)と考えて -Unique オプションは考えてこなかった。 でも今回の件は学びになった。 既存の Cmdlet で出来たとしても、長々とパイプラインを書かないといけないとかで複雑になったり、パフォーマンスが振るわないようであれば、機能に取り込んでも良さそうやなという感覚。

この -Unique は早速 0.13.0 でリリースして、自分の PowerShell profile で新しいオプション使うように直した。