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中使用它并不容易。
我正在使用 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中使用它并不容易。