PowerShellで巨大なファイルをGet-Contentし、Export-Csvするのを省メモリで行う

PowerShellで、ファイルを加工し、CSVファイルに出力する、ということを行った。

しかし、Get-Contentで取得したファイルを、下記の様にそのまま変数に突っ込むとファイル容量の数倍のメモリを確保しまう、というPowerShellの仕様が存在することがわかった。

$inputfile="c:\tmp\input.txt"
$inputdata=Get-Content $inputfile -Encoding UTF8 
foreach($line in $inputdata){
  いろんな処理~ 
}

しかも、配列を追加していく処理は重いらしく、かなり時間がかかる。

開発段階では、元のファイルサイズが1MB程度だったので気がつきませんでしたが、500MBや800MBになると、とんでもなくメモリを食っていました。

配列を確保しないで、次の処理を行わせるようにするには、パイプで繋いでForEach-Objectで回します。

$inputfile="c:\tmp\input.txt"
Get-Content $inputfile -Encoding UTF8 | ForEach-Object {
  $line=$_
  いろんな処理~
}

これにより、Get-Contentで読み込んだファイルに対するメモリ確保は行わずに、次の処理を行わせることができるようになります。

次に、各行を処理した結果をcsvファイルで出力する方法について改善します。

例えば、下記の様に出力結果を$results配列に入れ、それをまとめてexport-csvする、というのが簡単な実装でしょう。

$inputfile="c:\tmp\input.txt"
$outputfile="c:\tmp\output.csv"

$results=Get-Content $inputfile -Encoding UTF8 | ForEach-Object {
  $line=$_
  いろんな処理~
  $output = New-Object -TypeName PSObject
  $output | Add-Member -MemberType NoteProperty -Name "Name" -Value $name
  $output | Add-Member -MemberType NoteProperty -Name "Value" -Value $value
  $output | Add-Member -MemberType NoteProperty -Name "Details" -Value $details
  $output
}
$results | Export-Csv $outputfile -Encoding UTF8 -NoTypeInformation -Append -NoClobber

これも、input側と同じく、配列のメモリ確保問題があります。

しかし、Export-Csvは、配列を使わないと出力できませんし、ファイルの入出力処理を毎行実行するにはコストが高いです。 (ファイルを開く処理、閉じる処理というのは一般的に遅い)

1000行ぐらいであればメモリへの影響も少なく、また、1回当たりの書き込み負荷もそれほどではないようなので、1000行に1回、Export-Csvを実行するようにしたものが、下記です。

$inputfile="c:\tmp\input.txt"
$outputfile="c:\tmp\output.csv"

$results=@()
$linecount=0
Get-Content $inputfile -Encoding UTF8 | ForEach-Object {
  $line=$_
  いろんな処理~
  $output = New-Object -TypeName PSObject
  $output | Add-Member -MemberType NoteProperty -Name "Name" -Value $name
  $output | Add-Member -MemberType NoteProperty -Name "Value" -Value $value
  $output | Add-Member -MemberType NoteProperty -Name "Details" -Value $details
  $results+=$output

  $linecount++
  if(($linecount % 1000) -eq 0 ){
    $results | Export-Csv $outputfile -Encoding UTF8 -NoTypeInformation -Append -NoClobber
    $results = @()
  }
}
$results | Export-Csv $outputfile -Encoding UTF8 -NoTypeInformation -Append -NoClobber

これにより、読み込ませるファイルが大きい場合でも、PowerShellが使用するメモリ容量を少なく抑えることが可能となりました。


NetAppのperfstatデータ収集ツールで取得したファイルを分解する」で1行1出力で動かしてみたところ5時間かかりました。それを1000行まとめてから出力かけるように変更したところ2分で終わりました。

やはりファイル出力処理は重いですね。

“PowerShellで巨大なファイルをGet-Contentし、Export-Csvするのを省メモリで行う” への1件の返信

コメントを残す

メールアドレスが公開されることはありません。 が付いている欄は必須項目です

このサイトはスパムを低減するために Akismet を使っています。コメントデータの処理方法の詳細はこちらをご覧ください