Performance Powershell脚本的速度。寻求优化

Performance Powershell脚本的速度。寻求优化,performance,powershell,csv,if-statement,Performance,Powershell,Csv,If Statement,我有一个工作脚本,其目标是在导入到Oracle之前解析数据文件中的错误行。要处理一个450MB的csv文件,其中有8列,超过100万行,需要2.5个小时多一点,并且单个CPU内核的最大容量。小文件快速完成(在几秒钟内) 奇怪的是,一个行数和列数相近的350MB文件只需要30分钟 我的问题是,文件会随着时间的推移而增长,占用CPU 2.5小时是不好的。有人能推荐代码优化吗?一篇类似的标题文章推荐了本地路径——我已经在做了 $file = "\Your.csv" $path = "C:\Folde

我有一个工作脚本,其目标是在导入到Oracle之前解析数据文件中的错误行。要处理一个450MB的csv文件,其中有8列,超过100万行,需要2.5个小时多一点,并且单个CPU内核的最大容量。小文件快速完成(在几秒钟内)

奇怪的是,一个行数和列数相近的350MB文件只需要30分钟

我的问题是,文件会随着时间的推移而增长,占用CPU 2.5小时是不好的。有人能推荐代码优化吗?一篇类似的标题文章推荐了本地路径——我已经在做了

$file = "\Your.csv"

$path = "C:\Folder"

$csv  = Get-Content "$path$file"

# Count number of file headers
$count = ($csv[0] -split ',').count

# https://blogs.technet.microsoft.com/gbordier/2009/05/05/powershell-and-writing-files-how-fast-can-you-write-to-a-file/
$stream1 = [System.IO.StreamWriter] "$path\Passed$file-Pass.txt"
$stream2 = [System.IO.StreamWriter] "$path\Failed$file-Fail.txt"

# 2 validation steps: (1) count number of headers is ge (2) Row split after first col.  Those right hand side cols must total at least 40 characters.
$csv | Select -Skip 1 | % {
  if( ($_ -split ',').count -ge $count -And ($_.split(',',2)[1]).Length -ge 40) {
     $stream1.WriteLine($_)
  } else {
     $stream2.WriteLine($_) 
  }
}
$stream1.close()
$stream2.close()
示例数据文件:

C1,C2,C3,C4,C5,C6,C7,C8 ABC,000000000000006732,1063,2016-02-20,0,P,ESTIMATE,2015473497A10 ABC,000000000000006732,1110,2016-06-22,0,P,ESTIMATE,2015473497A10 ABC,,2016-06-22,,201501 ,,,,,,,, ABC,000000000000006732,1135,2016-08-28,0,P,ESTIMATE,2015473497B10 ABC,000000000000006732,1167,2015-12-20,0,P,ESTIMATE,2015473497B10 C1,C2,C3,C4,C5,C6,C7,C8 ABC,00000000000000 673210632016-02-20,0,P,估算,2015473497A10 ABC,00000000000000 673211102016-06-22,0,P,估算,2015473497A10 美国广播公司,2016-06-22,201501 ,,,,,,,, ABC,00000000000000 673211352016-08-28,0,P,估算,2015473497B10 ABC,00000000000000 673211672015-12-20,0,P,估算,2015473497B10
如果您想安装
awk
,您可以在一秒钟内完成1000000条记录-对我来说这似乎是一个很好的优化:-)

然后运行以下命令:

awk -F, -f commands.awk Your.csv

这个答案的其余部分与评论部分提到的“Beat hadoop with The shell”挑战有关,我想在某个地方保存我的代码,所以就在这里。。。。在我的iMac上以6.002秒的时间运行1543个文件中的3.5GB,总计约1.04亿条记录:

#!/bin/bash
doit(){
   awk '!/^\[Result/{next} /1-0/{w++;next} /0-1/{b++} END{print w,b}' $@
}

export -f doit
find . -name \*.pgn -print0 | parallel -0 -n 4 -j 12 doit {}

尝试尝试不同的循环策略,例如,切换到for循环可将处理时间缩短50%以上,例如:

[String]                 $Local:file           = 'Your.csv';
[String]                 $Local:path           = 'C:\temp';
[System.Array]           $Local:csv            = $null;
[System.IO.StreamWriter] $Local:objPassStream  = $null;
[System.IO.StreamWriter] $Local:objFailStream  = $null; 
[Int32]                  $Local:intHeaderCount = 0;
[Int32]                  $Local:intRow         = 0;
[String]                 $Local:strRow         = '';
[TimeSpan]               $Local:objMeasure     = 0;

try {
    # Load.
    $objMeasure = Measure-Command {
        $csv = Get-Content -LiteralPath (Join-Path -Path $path -ChildPath $file) -ErrorAction Stop;
        $intHeaderCount = ($csv[0] -split ',').count;
        } #measure-command
    'Load took {0}ms' -f $objMeasure.TotalMilliseconds;

    # Create stream writers.
    try {
        $objPassStream = New-Object -TypeName System.IO.StreamWriter ( '{0}\Passed{1}-pass.txt' -f $path, $file );
        $objFailStream = New-Object -TypeName System.IO.StreamWriter ( '{0}\Failed{1}-fail.txt' -f $path, $file );

        # Process CSV (v1).
        $objMeasure = Measure-Command {
            $csv | Select-Object -Skip 1 | Foreach-Object { 
                if( (($_ -Split ',').Count -ge $intHeaderCount) -And (($_.Split(',',2)[1]).Length -ge 40) ) {
                    $objPassStream.WriteLine( $_ );   
                } else {
                    $objFailStream.WriteLine( $_ );
                } #else-if
                } #foreach-object
            } #measure-command
        'Process took {0}ms' -f $objMeasure.TotalMilliseconds;

        # Process CSV (v2).
        $objMeasure = Measure-Command {
            for ( $intRow = 1; $intRow -lt $csv.Count; $intRow++ ) {
                if( (($csv[$intRow] -Split ',').Count -ge $intHeaderCount) -And (($csv[$intRow].Split(',',2)[1]).Length -ge 40) ) {
                    $objPassStream.WriteLine( $csv[$intRow] );   
                } else {
                    $objFailStream.WriteLine( $csv[$intRow] );
                } #else-if
                } #for
            } #measure-command
        'Process took {0}ms' -f $objMeasure.TotalMilliseconds;

        } #try
    catch [System.Exception] {
        'ERROR : Failed to create stream writers; exception was "{0}"' -f $_.Exception.Message;
         } #catch
    finally {
        $objFailStream.close();
        $objPassStream.close();    
        } #finally

   } #try
catch [System.Exception] {
    'ERROR : Failed to load CSV.';
    } #catch

exit 0;
  • 在默认模式下,当文件在所有PowerShell版本(包括5.1)上包含数百万行时,获取内容的速度非常慢,该模式会生成一个数组。更糟糕的是,您正在将其分配给一个变量,以便在读取整个文件并将其拆分为行之前,不会发生其他任何事情。在3.9GHz的Intel i7 3770K CPU上,
    $csv=Get Content$path
    读取一个包含800万行的350MB文件需要2分钟以上的时间

    解决方案:使用IO.StreamReader读取一行并立即处理。

    在PowerShell2中,StreamReader的优化程度不如在PS3+中,但仍比获取内容快


  • 通过
    |
    进行管道化的速度至少比通过
    while
    foreach
    语句(非cmdlet)等流控制语句直接枚举的速度慢几倍。
    解决方案:使用语句

  • 将每行拆分为字符串数组比只处理一个字符串慢。
    解决方案:使用
    IndexOf
    Replace
    方法(非运算符)计算字符出现次数

  • 使用循环时,PowerShell始终会创建内部管道。
    解决方案:在这种情况下,使用2-3倍的加速比

以下是PS2兼容代码。
在PS3+中速度更快(在我的电脑上,350MB的csv中,800万行需要30秒)

另一种方法是在两个过程中使用regex(不过比上面的代码慢)。
由于数组元素属性速记语法,需要PowerShell 3或更新版本:

$text = [IO.File]::ReadAllText('r:\data.csv')
$header = $text.substring(0, $text.indexOfAny("`r`n"))
$numCol = $header.split(',').count

$rx = [regex]"\r?\n(?:[^,]*,){$($numCol-1)}[^,]*?(?=\r?\n|$)"
[IO.File]::WriteAllText('r:\1.csv', $header + "`r`n" +
                                    ($rx.matches($text).groups.value -join "`r`n"))
[IO.File]::WriteAllText('r:\2.csv', $header + "`r`n" + $rx.replace($text, ''))

输入文件的几行示例如何?对你想做什么的描述?获取内容是超流的。使用IO.StreamReader。也可以为输出使用不同的硬盘驱动器,或者在StreamWriter构造函数中指定一个大的写缓冲区。这就是我希望得到的建议!好家伙,干杯。你能在代码的每个部分周围放置一些度量命令{
}块来看看延迟在哪里吗?i、 e:是加载、文件写入等等。我本来想试试Simon,但我想应该是If语句。读取文件需要一段时间,但在观看资源监视器时,我可以看到光盘写入在一段时间后开始。我还没有考虑到它可能是一边读一边写。我会在……不会受伤的!干杯。这是一个很好的答案,但不幸的是,这不是一个适用的解决方案。将记住未来的裁判。干杯。既然posh可以做得很好、更快,为什么还要用awk呢?比如看这个线程@4c74356b41,OP要求进行优化,我认为10000x的加速是一个非常好的优化。有时,最佳优化涉及不同的算法或不同的工具。如果,正如OP所指出的,他希望坚持使用Powershell,那么欢迎他忽略我的建议。我将离开,看看是否可以实现gawk。我想看看是否提供了特定于Powershell的解决方案。我完全同意你的“不同的工具”主张,但在这种情况下,我必须考虑支持能力。@ 4C7356B41有趣的挑战-我有一个小游戏,并可以舒适地击败他们的最佳时间8.7秒使用<代码> AWK < /代码>在6.002秒。我不知道我的系统和他们的系统相比有多明显,但我的只是一个桌面iMac,没有什么特别的。很好的答案@wOxxOm。。。清晰简洁地解释了为什么使用的方法很慢,以及应该使用什么来代替。运行时间从几个小时变为几分钟。@felixmc,由于调用命令,速度又提高了2倍!请参阅更新的代码。“在PowerShell2中,StreamReader的优化程度不如在PS3+中,但仍比获取内容快”——这是怎么回事?StreamReader不是来自.NET而不是PowerShell吗?我很好奇,因为我以为我是在从PowerShell调用本机.NET类,而不是我实际上可能正在做的其他一些奇怪的构造???这是一个非常好的答案@Simon Catlin-我可以看到我返回并用于测试的很棒的模板。在第二块上减少50%的时间。棒 极 了
[String]                 $Local:file           = 'Your.csv';
[String]                 $Local:path           = 'C:\temp';
[System.Array]           $Local:csv            = $null;
[System.IO.StreamWriter] $Local:objPassStream  = $null;
[System.IO.StreamWriter] $Local:objFailStream  = $null; 
[Int32]                  $Local:intHeaderCount = 0;
[Int32]                  $Local:intRow         = 0;
[String]                 $Local:strRow         = '';
[TimeSpan]               $Local:objMeasure     = 0;

try {
    # Load.
    $objMeasure = Measure-Command {
        $csv = Get-Content -LiteralPath (Join-Path -Path $path -ChildPath $file) -ErrorAction Stop;
        $intHeaderCount = ($csv[0] -split ',').count;
        } #measure-command
    'Load took {0}ms' -f $objMeasure.TotalMilliseconds;

    # Create stream writers.
    try {
        $objPassStream = New-Object -TypeName System.IO.StreamWriter ( '{0}\Passed{1}-pass.txt' -f $path, $file );
        $objFailStream = New-Object -TypeName System.IO.StreamWriter ( '{0}\Failed{1}-fail.txt' -f $path, $file );

        # Process CSV (v1).
        $objMeasure = Measure-Command {
            $csv | Select-Object -Skip 1 | Foreach-Object { 
                if( (($_ -Split ',').Count -ge $intHeaderCount) -And (($_.Split(',',2)[1]).Length -ge 40) ) {
                    $objPassStream.WriteLine( $_ );   
                } else {
                    $objFailStream.WriteLine( $_ );
                } #else-if
                } #foreach-object
            } #measure-command
        'Process took {0}ms' -f $objMeasure.TotalMilliseconds;

        # Process CSV (v2).
        $objMeasure = Measure-Command {
            for ( $intRow = 1; $intRow -lt $csv.Count; $intRow++ ) {
                if( (($csv[$intRow] -Split ',').Count -ge $intHeaderCount) -And (($csv[$intRow].Split(',',2)[1]).Length -ge 40) ) {
                    $objPassStream.WriteLine( $csv[$intRow] );   
                } else {
                    $objFailStream.WriteLine( $csv[$intRow] );
                } #else-if
                } #for
            } #measure-command
        'Process took {0}ms' -f $objMeasure.TotalMilliseconds;

        } #try
    catch [System.Exception] {
        'ERROR : Failed to create stream writers; exception was "{0}"' -f $_.Exception.Message;
         } #catch
    finally {
        $objFailStream.close();
        $objPassStream.close();    
        } #finally

   } #try
catch [System.Exception] {
    'ERROR : Failed to load CSV.';
    } #catch

exit 0;
$reader = New-Object IO.StreamReader ('r:\data.csv', [Text.Encoding]::UTF8, $true, 4MB)
$header = $reader.ReadLine()
$numCol = $header.Split(',').count

$writer1 = New-Object IO.StreamWriter ('r:\1.csv', $false, [Text.Encoding]::UTF8, 4MB)
$writer2 = New-Object IO.StreamWriter ('r:\2.csv', $false, [Text.Encoding]::UTF8, 4MB)
$writer1.WriteLine($header)
$writer2.WriteLine($header)

Write-Progress 'Filtering...' -status ' '
$watch = [Diagnostics.Stopwatch]::StartNew()
$currLine = 0

Invoke-Command { # the speed-up trick: disables internal pipeline
while (!$reader.EndOfStream) {
    $s = $reader.ReadLine()
    $slen = $s.length
    if ($slen-$s.IndexOf(',')-1 -ge 40 -and $slen-$s.Replace(',','').length+1 -eq $numCol){
        $writer1.WriteLine($s)
    } else {
        $writer2.WriteLine($s)
    }
    if (++$currLine % 10000 -eq 0) {
        $pctDone = $reader.BaseStream.Position / $reader.BaseStream.Length
        Write-Progress 'Filtering...' -status "Line: $currLine" `
            -PercentComplete ($pctDone * 100) `
            -SecondsRemaining ($watch.ElapsedMilliseconds * (1/$pctDone - 1) / 1000)
    }
}
} #Invoke-Command end

Write-Progress 'Filtering...' -Completed -status ' '
echo "Elapsed $($watch.Elapsed)"

$reader.close()
$writer1.close()
$writer2.close()
$text = [IO.File]::ReadAllText('r:\data.csv')
$header = $text.substring(0, $text.indexOfAny("`r`n"))
$numCol = $header.split(',').count

$rx = [regex]"\r?\n(?:[^,]*,){$($numCol-1)}[^,]*?(?=\r?\n|$)"
[IO.File]::WriteAllText('r:\1.csv', $header + "`r`n" +
                                    ($rx.matches($text).groups.value -join "`r`n"))
[IO.File]::WriteAllText('r:\2.csv', $header + "`r`n" + $rx.replace($text, ''))