PowerShellのWhere-Object句のパフォーマンスが遅いので対応例

PowerShellパイプラインは手元で使っている分には凄い便利ですが、大量のデータを扱うとしばしばパフォーマンスがとても遅くなる場合があります。

PowerShellのWhere-Objectは便利なので使うことが多いと思います。しかし大きな処理をすると問題になることが多い部分です。例えば手元にあった以下のようなスクリプトの場合はWhere-Objectをforeachで書き換えただけで5分の1程度に処理時間が短縮できました。

Whereパターン

$filteredEntries = $10000Entries | Where{($_.Key -eq $Key) -and ($_.Id -eq $id) -and ($_.Value)}

書き直しパターン

foreach($e in $10000Entries)
{
    if(($e.Key -eq $Key) -and ($e.Id -eq $id) -and ($e.Value))
    {
        $filteredEntries += $e
    }	
}

 
 


計測してみた

しっかり計測してみました。

$loop = 10000
$randomNums = @()
for ($i = 1; $i -lt $loop; $i++)
{ 
    $randomNums += Get-Random -Maximum 99999 -Minimum 10000    
}

$nums1 = @()
$nums2 = @()
$nums3 = @()
$result1 = (Measure-Command{
            $nums1 = $randomNums | Where-Object{$_ -match "23"}
      })

$result2 = (Measure-Command{
            #この方法だけSystem.Collections.ObjectModel.Collection`の型になります
            $nums2 = $randomNums.Where({$_ -match "23"})
      })

$result3 = (Measure-Command{
            foreach ($num in $randomNums)
            {
                if($num -match "23")
                {
                    $nums3 += $num
                }
            }
      })


"1. Where-Object  :{0}" -f $result1.TotalMilliseconds 
"2. where Method  :{0}" -f $result2.TotalMilliseconds
"3. foreach句   :{0}" -f $result3.TotalMilliseconds

 

結果は下の表の通り明らかにforeachステートメントが一番早いですね。ISEにコピペで検証できるのでやってみてください。

No 方法 1回目 2回目 3回目
1 Where-Object 315.2737 407.667 324.9565
2 where Method 103.7537 136.2756 106.9086
3 foreach句 54.4612 61.2299 46.6986

まとめ

コンソールからダイアログで作業する分にはとても便利なパイプラインですが、大きな処理の場合は無視できない遅延を招く可能性があります。その場合は、foreach句の利用も検討した方が良いかもしれません。