【问题标题】:Slow Powershell script for CSV modification用于 CSV 修改的慢速 Powershell 脚本
【发布时间】:2013-06-26 09:48:46
【问题描述】:

我正在使用 powershell 脚本将数据附加到一堆文件的末尾。 每个文件是一个大约 50Mb 的 CSV(比如 200 万行),大约有 50 个文件。

我正在使用的脚本如下所示:

$MyInvocation.MyCommand.path

$files = ls *.csv 

foreach($f in $files) 
{
$baseName = [System.IO.Path]::GetFileNameWithoutExtension($f)
$year = $basename.substring(0,4)

Write-Host "Starting" $Basename

$r = [IO.File]::OpenText($f)
while ($r.Peek() -ge 0) {
    $line = $r.ReadLine()
    $line + "," + $year | Add-Content $(".\DR_" + $basename + ".CSV")
}
$r.Dispose()

}

问题是,它很慢。通过它们大约需要12个小时。 它不是超级复杂,所以我不希望它需要那么长时间才能运行。 我可以做些什么来加快速度?

【问题讨论】:

    标签: powershell csv


    【解决方案1】:

    逐行读取和写入文件可能有点慢。也许您的防病毒软件也会导致速度缓慢。使用Measure-Command 查看脚本的哪些部分是慢的。

    作为一般建议,与其写很多小块,不如写几个大块。您可以通过在 StringBuilder 中存储一些内容并将其内容附加到输出文件中来实现这一点,例如每处理 1000 行。像这样,

    $sb = new-object Text.StringBuilder # New String Builder for stuff
    $i = 1 # Row counter
    while ($r.Peek() -ge 0) {
        # Add formatted stuff into the buffer
        [void]$sb.Append($("{0},{1}{2}" -f $r.ReadLine(), $year, [Environment]::NewLine ) )
    
        if(++$i % 1000 -eq 0){ # When 1000 rows are added, dump contents into file
          Add-Content $(".\DR_" + $basename + ".CSV") $sb.ToString()
          $sb = new-object Text.StringBuilder # Reset the StringBuilder
        }
    }
    # Don't miss the tail of the contents
    Add-Content $(".\DR_" + $basename + ".CSV") $sb.ToString()
    

    【讨论】:

    • 这是一个显着的速度提升。谢谢。
    • 但是它每次都会产生一个额外的换行符。我已经解决了这个问题(只是现在),只是不使用将每 100 行转储到文件中并在最后全部写入的 if 部分。
    【解决方案2】:

    当存在可以对对象执行工作的 cmdlet 时,不要使用 .NET Framework 静态方法和构建字符串。收集您的数据,添加年份列,然后导出到您的新文件。您还要执行大量文件 I/O,这也会减慢您的速度。

    这可能需要更多内存。但它一次读取整个文件,一次写入整个文件。它还假定您的 CSV 文件具有列标题。但让其他人更容易查看并准确理解正在发生的事情(编写脚本以便阅读!)。

    # Always use full cmdlet names in scripts, not aliases
    $files = get-childitem *.csv;
    
    foreach($f in $files) 
    {
        #basename is a property of the file object in PowerShell, there's no need to call a static method
        $basename = $f.basename;
        $year = $f.basename.substring(0,4)
    
        # Every time you use Write-Host, a puppy dies
        "Starting $Basename";
    
        # If you've got CSV data, treat it as CSV data. PowerShell can import it into a collection natively.
        $data = Import-Csv $f;
        $exportData = @();
        foreach ($row in $data) {
    # Add a year "property" to each row object
            $row |Add-Member -membertype NoteProperty -Name "Year" -Value $year;
    # Export the modified row to the output file
            $row |Export-Csv -NoTypeInformation -Path $("r:\DR_" + $basename + ".CSV") -Append -NoClobber
        }
    }
    

    【讨论】:

    • 谢谢,一些 cmets 提供了非常丰富的信息。但是,我的原始脚本看起来很像这样。我改变它的原因是它太耗内存而且太慢了。实际上令人费解的是,运行这个脚本似乎只为一个 50MB 的 CSV 文件使用了超过 1 GB 的内存。知道为什么吗?
    • 内存量似乎过多。我本质上是将文件的 两个 副本放在内存中,但它仍然是大量的内存。我现在做了一个编辑,它将每一行连续转储到磁盘,而不是全部收集。这将对 I/O 产生性能影响,但内存使用量应该更少。您也可以与另一个答案混合使用,一次收集 X 条记录,然后将它们作为一个组导出到文件中。
    猜你喜欢
    • 2015-11-22
    • 2018-11-26
    • 2021-09-30
    • 2022-06-21
    • 1970-01-01
    • 2021-08-15
    • 1970-01-01
    • 2015-05-18
    • 2012-07-16
    相关资源
    最近更新 更多