2017-11-17 178 views
2

大規模なCSVファイルをたくさんマージしています。主要なジャンクをスキップして、各行にファイル名を追加しているとき:PowerShell Get-Contentの基本操作が遅い

Get-ChildItem . | Where Name -match "Q[0-4]20[0-1][0-9].csv" | 
Foreach-Object { 
    $file = $_.BaseName 
    Get-Content $_.FullName | select-object -skip 3 | % { 
     "$_,${file}" | Out-File -Append temp.csv -Encoding ASCII 
    } 
} 

PowerShellではこれでもi7の/最大16GB機(〜5メガバイト/分)に非常に遅いです。より効率的にすることはできますか。 Python?あなたが最初にメモリ内の配列にすべてをかける場合

+4

あなたのボトルネックは、あなたが 'Out-File -Append'を使っていることです。つまり、あなたのcsvの各行のディスクに書き込みます。より良いアプローチは、すべてのファイルをメモリにロードし、必要な処理を行い、マージされたcsvを一度に出力することです。 – gms0ulman

+0

ああ、ありがとう、これはコンテンツを追加しますか?私はそれを行ってみよう –

+0

Add-Contentはスピードに差はなかったが、各行にファイル名を追加すると速度が上がったので、それはボトルネックだと思う。残念ながら私はこれを最終的に行うことができないかもしれません) –

答えて

3

Get-Content/Set-Contentは、大きなファイルと恐ろしいです。パフォーマンスが重要な場合は、ストリームが良い選択肢です。そのことを念頭に置いて、各ファイルを読み込み、結果を書き出すことができます。

編集ストリームを各ファイルに出力するには、書き込みストリーム$streamWriterを作成します。ファイルを読み込んでファイルを大きなバッチで書き込むことができますが、それは速くなりますが、いくつかの行を無視してそれぞれの行を変更して行単位で処理する方が簡単です。この間にコンソールに何も書き込まないようにしてください。

'{0},"{1}"' -f $_,$fileとは、ベース名にスペースが含まれている場合に追加される最後の「列」です。

2
Measure-Command -Expression { 
    Get-ChildItem C:\temp | Where Name -like "*.csv" | ForEach-Object { 
     $file = $_.BaseName 
     Get-Content $_.FullName | select-object -Skip 3 | ForEach-Object { 
      "$_,$($file)" | Out-File -Append C:\temp\t\tempe1.csv -Encoding ASCII -Force 
     } 
    } 
} # TotalSeconds  : 12,0526802 for 11415 lines 

、物事は非常に速く行く:

Measure-Command -Expression { 
    $arr = @() 
    Get-ChildItem C:\temp | Where Name -like "*.csv" | ForEach-Object { 
     $file = $_.BaseName 
     $arr += Get-Content $_.FullName | select-object -Skip 3 | ForEach-Object { 
      "$_,$($file)" 
     } 
    } 
    $arr | Out-File -Append C:\temp\t\tempe2.csv -Encoding ASCII -Force 
} # TotalSeconds  : 0,8197193 for 11415 lines 

EDIT:あなたのファイル名は、それぞれの行に追加されたように、それを修正しました。あなたは、バッファの配列変数を使用することができ、あなたのスクリプトのパフォーマンスを台無しに-Appendを避けるために

+0

ありがとう、私は後でチェックします。私のテストケースでも1.5mの行があるので、RAMを増やす必要がある場合は追加のバッチ処理が必要になるかもしれません。 –

+3

この設定では、パフォーマンスが大幅に向上するので、 '$ arr + = ...'は実行しないでください。既にパイプラインを使用しています。その論理を一括して取り除き、直ちに 'out-file' /' set-content'に送ります。 – Matt

1

# Initialize buffer 
$csvBuffer = @() 

Get-ChildItem *.csv | Foreach-Object { 
    $file = $_.BaseName 
    $content = Get-Content $_.FullName | Select-Object -Skip 3 | %{ 
     "$_,${file}" 
    } 

    # Populate buffer 
    $csvBuffer += $content 

    # Write buffer to disk if it contains 5000 lines or more 
    $csvBufferCount = $csvBuffer | Measure-Object | Select-Object -ExpandProperty Count 
    if($csvBufferCount -ge 5000) 
    { 
     $csvBuffer | Out-File -Path temp.csv -Encoding ASCII -Append 
     $csvBuffer = @() 
    } 
} 

# Important : empty the buffer remainder 
if($csvBufferCount -gt 0) 
{ 
    $csvBuffer | Out-File -Path temp.csv -Encoding ASCII -Append 
    $csvBuffer = @() 
} 
関連する問題