Powershell 中的缓慢数组操作

Slow array operation in Powershell

我正在使用 PowerShell 脚本将 ping 日志转换为图表数据。

脚本运行良好,但 运行 由于数组操作导致速度非常慢。

如果脚本在 10k 行的文件上执行,大约需要 7 秒。 如果删除数组操作,则只需不到一秒即可完成。

我正在寻找 return 在不使用临时数组的情况下向调用函数发送数据的替代解决方案。

输入日志示例:

02.01.2017-14:53:54> Reply from 8.8.8.8: bytes=32 time=184ms TTL=57
02.01.2017-14:53:54> Reply from 8.8.8.8: bytes=32 time=18ms TTL=57
02.01.2017-14:53:59> Request timed out.
02.01.2017-14:54:01> Reply from 192.168.2.186: Destination host unreachable.
02.01.2017-14:54:05> Request timed out.
02.01.2017-14:54:07> Reply from 192.168.2.186: Destination host unreachable.

脚本:

function Convert-V4PingLog2ChartData
{
    param($V4PingLogFile, $AvarageRespondTime, $ChartCounter)
    $ConvertedData=""
    $var=Get-Content $V4PingLogFile
    $varArray=$var.split("`n")
    $varArray=$varArray | Select-Object -Skip 2

    $CommandExecuteTime=Measure-Command{

    $pattern = "^([0-9]{2})\.([0-9]{2})\.([0-9]{4})-([0-9]{2}):([0-9]{2}):([0-9]{2})> Reply from [0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}: bytes=32 time=([0-9]{1,4})ms TTL=[0-9]{1,3}$";
    $pattern2="^([0-9]{2})\.([0-9]{2})\.([0-9]{4})-([0-9]{2}):([0-9]{2}):([0-9]{2})> (Request timed out.|Reply from [0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}: Destination host unreachable.)$"

    foreach($nextLine in $varArray)
    {
        if($nextLine -like "* time=*")    
        {
            $ConvertedData+=$nextLine -replace $pattern, "data$ChartCounter.addRow([new Date(`, `, `, `, `, `,  00), `, $AvarageRespondTime]);"
        }
        else
        {
            $ConvertedData+=$nextLine -replace $pattern2, "data$ChartCounter.addRow([new Date(`, `, `, `, `, `,  00), 0, $AvarageRespondTime]);"
        }
    }
    }
    Write-Host $CommandExecuteTime
    return $ConvertedData
}

实际上,您是在追加到一个字符串,而不是一个数组,但这肯定也很慢。

改变这个:

$ConvertedData=""
...
foreach($nextLine in $varArray)
{
    if($nextLine -like "* time=*")    
    {
        $ConvertedData+=$nextLine -replace ...
    }
    else
    {
        $ConvertedData+=$nextLine -replace ...
    }
}

进入这个:

$ConvertedData = foreach ($nextLine in $varArray) {
    if ($nextLine -like "* time=*") {
        $nextLine -replace ...
    } else {
        $nextLine -replace ...
    }
}
$ConvertedData -join "`n"

加快速度。

你好试试这样的东西:

$template=@"
{Timelog*:02.01.2017-14:53:54}> {TypeRow:Reply} {Detail:from {IP:8.8.8.8}: {Detailbytes:bytes=32} {Detailtime:time=184ms} {DetailTTL:TTL=57}}
{Timelog*:15.15.2018-20:30:00}> {TypeRow:Reply} {Detail:from {IP:18.28.38.48}: {Detailbytes:bytes=32} {Detailtime:time=184ms} {DetailTTL:TTL=57}}
{Timelog*:02.01.2017-14:53:54}> {TypeRow:Reply} {Detail:from {IP:1.2.345.678}: {Detailbytes:bytes=32} {Detailtime:time=184ms} {DetailTTL:TTL=57}}
{Timelog*:04.01.2017-14:53:59}> {TypeRow:Request} {Detail:{Message:timed out.}}
{Timelog*:03.01.2017-14:54:01}> {TypeRow:Reply} {Detail:from {IP:192.168.2.186}: {Message:Destination host unreachable.}}
{Timelog*:12.01.2017-14:54:05}> {TypeRow:Request} {Detail:{Message:timed out.}}
{Timelog*:02.01.2017-14:54:07}> {TypeRow:Reply} {Detail:from {IP:255.255.255.255}: {Message:Destination host unreachable.}}
"@

get-Content C:\temp\File.txt | ConvertFrom-String -TemplateContent $template |
%{
    [pscustomobject]@{
    Timelog=$_.Timelog
    TypeRow=$_.TypeRow
    IP=$_.Message.IP
    Detailbytes=if ($_.Detail.Message -ne $null) {''} else {$_.Detail.Detailbytes}
    Detailtime=if ($_.Detail.Message -ne $null) {''} else {$_.Detail.Detailtime}
    DetailTTL=if ($_.Detail.Message -ne $null) {''} else {$_.Detail.DetailTTL}
    Error=$_.Detail.Message 
    }
} | Format-Table

首先,正如 Ansgar 所写,您要添加到字符串而不是数组。但是在这两种情况下问题是一样的。在 .NET 中,数组和字符串都是不可变的(数组大小,而不是内容)。每次向数组或字符串追加内容时,系统都会将旧内容复制到新的内存位置,然后追加新数据。

在数组中,如果您手动将数组的大小调整为您期望的最终大小,则可以解决此问题。调整大小复制数组, 但它只做一次而不是每次追加。速度差异可以巨大

让我们考虑以下将 10000 项附加到空数组的命令:

$a = @(); Measure-Command { for($i = 0; $i -lt 10000; $i++) { $a += $i } }

运行这个命令我得到了如下结果

Days              : 0
Hours             : 0
Minutes           : 0
Seconds           : 3
Milliseconds      : 534
Ticks             : 35342407
TotalDays         : 4,09055636574074E-05
TotalHours        : 0,000981733527777778
TotalMinutes      : 0,0589040116666667
TotalSeconds      : 3,5342407
TotalMilliseconds : 3534,2407

现在考虑以下命令。它首先使用 Array 对象的 Resize 静态成员函数调整数组的大小,然后使用索引设置 10000。

$b = @(); Measure-Command { 
    [array]::Resize([ref]$b,10000); 
    for($i = 0; $i -lt 10000; $i++) { $b[$i] = $i } 
}

结果是:

Days              : 0
Hours             : 0
Minutes           : 0
Seconds           : 0
Milliseconds      : 40
Ticks             : 402365
TotalDays         : 4,65700231481481E-07
TotalHours        : 1,11768055555556E-05
TotalMinutes      : 0,000670608333333333
TotalSeconds      : 0,0402365
TotalMilliseconds : 40,2365

运行 时间从 3.5 秒减少到仅 40 毫秒!

您可以将此技术与 Ansgar 的技术相结合。调整数组大小,将结果添加到调整大小的数组中,最后将数组连接成一个巨大的字符串。

一些评论。您可以根据需要多次调用调整大小,每次都提供新的数组大小。您可以使用数组的长度 属性 获取当前数组大小。 如果达到限制并且需要更多 space,只需调用调整大小并添加另一个大块。

我不认为你会看到很大的改进,但是如果你 really-really 想尽快进行,你应该去 MSDN 看看添加到字典 class.对于这种想法,推荐class,但从PowerShell中使用它并不容易。