【问题标题】:Making an Powershell Progress Bar more efficient使 Powershell 进度条更高效
【发布时间】:2021-06-15 07:02:39
【问题描述】:

我构建了一个脚本来过滤日志文件中的多个消息。我现在使用的文件大约有 400.000 行,当我用以下代码寻找匹配项时,他需要很长时间,因为我添加了进度条。有没有办法让它更有效率。如果我是对的,那么需要这么长时间的原因是他通过的每一行都会刷新进度条 Gui。

$i= 0
$path = ""
$length = (Get-Content $path).Length

#Datum, Hostname und Message Nummer
$result = Get-Content $path | ForEach-Object {
    if($_ -match '(\d{2}\.\d{2}\.\d{4} \d{2}:\d{2}:\d{2}).*\(((?:\d{1,3}\.){3}\d{1,3})\) disconnected\.?\s+(\d+) message\[s\]'){
        try {
            $dns = [System.Net.Dns]::GetHostEntry($matches[2]).HostName
        }
        catch { 
            $dns = 'Not available' 
        }
        [PsCustomObject]@{
            IP       = $matches[2]
            Messages = [int]$matches[3]
            DNSName  = $dns
            Date     = [datetime]::ParseExact($matches[1], 'dd.MM.yyyy HH:mm:ss', $null)
        }
    }
     # update counter and write progress
   $i++
   Write-Progress -activity "Searching for matches" -status "Scanned: $i of $($length)" -percentComplete (($i / $length)  * 100)
 }

 #Messages Counted
 $cumulative = $result | Group-Object -Property IP | ForEach-Object {
    [PsCustomObject]@{
        IP = $_.Name
        Messages = ($_.Group | Measure-Object -Property Messages -Sum).Sum
        DNSName = $_.Group[0].DNSName
        Date    = ($_.Group | Sort-Object Date)[-1].Date
    }
}

【问题讨论】:

  • Write-Progress 速度很慢,您可能会考虑调用它的次数与屏幕宽度一样多(比如说 200 个字符):if ($i % [math]::floor($length / 200) -eq 0) { Write-Process ...

标签: powershell foreach progress-bar


【解决方案1】:

在宿主应用程序中更新进度条元素确实会在执行过程中占用时间和资源 - 但即使您取消了进度条,写入进度流仍然很慢!

作为iRon suggests,解决办法是少打电话给Write-Progress

$result = Get-Content $path | ForEach-Object {
    # ...
    
    $i++
    if($i % 100 -eq 0){
        Write-Progress -activity "Searching for matches" -status "Scanned: $i of $($length)" -percentComplete (($i / $length)  * 100)
    }
}

这里我们仅每 100 行写入一次进度流 - 影响执行速度的更新减少了 99% :-)

【讨论】:

    【解决方案2】:

    详细说明我之前的命令:

    Write-Progress 速度很慢,尤其是在 Windows PowerShell 上。换句话说,如果您使用 Windows PowerShell,我建议您升级(或至少检查)PowerShell Core

    为了加快速度,您可能会考虑调用 Write-Progress 的次数大约为您的最大屏幕宽度:

    $Length = 400.000
    (Measure-Command {
        $MaxScreenWidth = 200
        ForEach($i in (0..$Length)) {
            if ($i % [math]::floor($length / $MaxScreenWidth) -eq 0) { 
                Write-Progress -activity "Searching for matches" -status "Scanned: $i of $($length)" -percentComplete (($i / $length)  * 100)
            }
        }
    }).TotalMilliSeconds
    

    对于这个简单的示例,这大约是不使用 if ($i % [math]::floor($length / $MaxScreenWidth) -eq 0) { 条件时的两倍(在 Windows PowerShell 上),对于更多迭代或同时输出到显示器时甚至更快。

    注意事项:

    • 上面的例子假设一个连续的序列
    • 假定序列大于最大屏幕宽度
    • 在进度过程中不应更改(缩小)屏幕宽度

    为避免上述缺点,您可能会选择更复杂的实现,例如:

    $Length = 400.000
    (Measure-Command {
        ForEach($i in (0..$Length)) {
            $Script:WindowWidthChanged = $Script:WindowWidth -ne $Host.UI.RawUI.WindowSize.Width
            if ($Script:WindowWidthChanged) { $Script:WindowWidth = $Host.UI.RawUI.WindowSize.Width }
            $ProgressCompleted = [math]::floor($i * $Script:WindowWidth / $length)
            if ($Script:WindowWidthChanged -or $ProgressCompleted -ne $Script:LastProgressCompleted) {
                Write-Progress -activity "Searching for matches" -status "Scanned: $i of $($length)" -percentComplete (($i / $length)  * 100)
            }
            $Script:LastProgressCompleted = $ProgressCompleted
        }
    }).TotalMilliSeconds
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2016-05-19
      • 1970-01-01
      • 1970-01-01
      • 2019-10-10
      • 2012-07-03
      • 2018-05-19
      相关资源
      最近更新 更多