当前位置:网站首页 > 更多 > 玩电脑 > 正文

[玩转系统] 理解 PowerShell 7 中的并行 ForEach-Object

作者:精品下载站 日期:2024-12-14 07:55:28 浏览:15 分类:玩电脑

理解 PowerShell 7 中的并行 ForEach-Object


您很可能在 PowerShell 7 Personally 的最新预览版中听说过 ForEach-Object 的新 -parallel 参数。我等这个已经很久了。我过去只使用 PowerShell 工作流程,因为它提供了一种并行运行命令的方法。将此功能作为语言的一部分是一个受欢迎的补充。但这不是魔法,当你使用它时,会产生现实世界的后果。 -Parallel 参数将启动一组运行空间并在每个运行空间中运行脚本块。并行运行并不意味着按顺序运行。

这是新参数的实际应用示例。

[玩转系统] 理解 PowerShell 7 中的并行 ForEach-Object

我引入了随机睡眠间隔来帮助证明命令是并行运行的。每个并行操作完成时,结果都会写入管道。让我们更深入地研究一下这个问题。

直管

这是使用 ForEach-Object 的直接管道示例。

Measure-Command {
    1..5000 | ForEach-Object { [math]::Sqrt($_) * 2 }
}

我花了 34 毫秒才完成。这是相同的命令,但使用 -parallel 运行。

Measure-Command {
    1..5000 | ForEach-Object -parallel { [math]::Sqrt($_) * 2 }
}

这花了 89。显然,使用此参数时会产生开销。

然而,还有潜在的改进空间。默认情况下,该命令一次启动 5 个运行空间。您可以使用 -ThrottleLimit 参数增加或减少此值。

Measure-Command {
    1..5000 | ForEach-Object -parallel { [math]::Sqrt($_) * 2 } -throttlelimit 25
}

这个版本几乎用了5秒就完成了!相比之下,使用 ForEach 枚举器:

Measure-Command {
    foreach ($x in (1..5000)) { [math]::Sqrt($x) * 2 }
}

大约需要 7 毫秒。

运行脚本块

让我们增加复杂性并做一些更实际的事情。这是一个脚本块,它将为我提供一个显示给定文件夹中文件使用情况的对象。

$t = {
    param ([string]$Path)
    Get-ChildItem -path $path -recurse -file -erroraction SilentlyContinue |
        Measure-Object -sum -Property Length -Maximum -Minimum |
        Select-Object @{Name = "Computername"; Expression = { $env:COMPUTERNAME } },
        @{Name = "Path"; Expression = { Convert-Path $path } },
        Count, Sum, Maximum, Minimum
}

Measure-Command {
    "c:\work", "c:\scripts", "d:\temp", "C:\users\jeff\Documents", "c:\windows" | 
    ForEach-Object -process { Invoke-Command -scriptblock $t -argumentlist $_ }
}

在我的具有 32GB 内存的 Windows 10 桌面上本地运行此程序花费了 34 秒多一点的时间。这是使用 -Parallel 的相同命令。

Measure-Command {
    "c:\work", "c:\scripts", "d:\temp", "C:\users\jeff\Documents", "c:\windows" | ForEach-Object -parallel {
        $path = $_
        Get-ChildItem -path $path -recurse -file -erroraction SilentlyContinue |
            Measure-Object -sum -Property Length -Maximum -Minimum |
            Select-Object @{Name = "Computername"; Expression = { $env:COMPUTERNAME } },
            @{Name = "Path"; Expression = { Convert-Path $path } },
            Count, Sum, Maximum, Minimum

    } 
}

您应该注意到语法上有很大差异。因为每个并行脚本块都在新的运行空间中运行,所以传递像 $t 这样的变量会更困难。有一些方法可以解决这个问题。对我来说,演示最简单的解决方案是简单地复制脚本块。

该命令运行大约需要 50 秒。不过,如果我将油门限制调到 6,它会在大约 20 秒内完成。

远程命令

-Parallel 开始有意义的地方是涉及远程计算机的命令或任何包含某种形式的可变性的命令。这是您可能运行的传统命令。

Measure-Command {
    'srv1', 'srv2', 'win10', 'dom1', 'srv1', 'srv2', 'win10', 'dom1' | ForEach-Object {
        Get-WinEvent -FilterHashtable @{Logname = "system"; Level = 2, 3 } -MaxEvents 100 -ComputerName $_
    }
}

我只有几个虚拟机可供测试,因此我对它们重复该命令。大约 3 秒内完成。与 -Parallel 方法相比:

Measure-Command {
    'srv1', 'srv2', 'win10', 'dom1', 'srv1', 'srv2', 'win10', 'dom1' | ForEach-Object -parallel {
        Get-WinEvent -FilterHashtable @{Logname = "system"; Level = 2, 3 } -MaxEvents 100 -ComputerName $_
    }
}

花费了不到 1 秒的时间。让我们增加样本量。

Measure-Command {
    $d = ('srv1', 'srv2', 'win10', 'dom1')*5 | 
    ForEach-Object {
        Get-WinEvent -FilterHashtable @{Logname = "system"; Level = 2, 3 }  -ComputerName $_
    }
}

与此相比,这花了 9.7 秒:

Measure-Command {
    $d = ('srv1', 'srv2', 'win10', 'dom1')*5  |
        ForEach-Object -parallel {
            Get-WinEvent -FilterHashtable @{Logname = "system"; Level = 2, 3 }  -ComputerName $_
        }
}

3.7 秒内完成

大规模 PowerShell

让我们真正看看这在规模上是什么样子。我什至会尝试模拟服务器和网络延迟。

Measure-Command {
    $d = ('srv1', 'srv2', 'win10', 'dom1') * 100 | ForEach-Object {
        Get-WinEvent -FilterHashtable @{Logname = "system"; Level = 2, 3 }  -ComputerName $_
        #simulate network/server latency
        Start-Sleep -Seconds (Get-Random -Minimum 1 -Maximum 5)
    }
}

在我的桌面上,这需要超过 18 分钟才能完成,返回近 41,000 条记录。以下是使用 -parallel 的默认 PowerShell 7 版本:

Measure-Command {
    $d = ('srv1', 'srv2', 'win10', 'dom1') * 100 | ForEach-Object -parallel {
        Get-WinEvent -FilterHashtable @{Logname = "system"; Level = 2, 3 }  -ComputerName $_
        Start-Sleep -Seconds (Get-Random -Minimum 1 -Maximum 5)
    }
}

现在命令在 3 分 35 秒内完成。

默认油门限制值可能会更改。杰弗里·斯诺弗 (Jeffrey Snover) 表示他认为应该更高,我想我同意。让我们看看使用 Invoke-Command 中的默认 ThrottleLimit 值时会发生什么。

Measure-Command {
    $d = ('srv1', 'srv2', 'win10', 'dom1') * 100 | ForEach-Object -parallel {
        Get-WinEvent -FilterHashtable @{Logname = "system"; Level = 2, 3 }  -ComputerName $_
        Start-Sleep -Seconds (Get-Random -Minimum 1 -Maximum 5)
    } -throttlelimit 32
}

这在 49 秒内完成!

工作流程替代方案

最后一个比较是一个公认的粗糙工作流程,与其他示例相似。我切换到 Get-Eventlog 因为我认为工作流程不喜欢通过序列化连接发送过滤哈希表。

Workflow GetLogs {

    Param()

    Sequence {
        $computers = ('srv1.company.pri', 'srv2.company.pri', 'win10.company.pri', 'dom1.company.pri') * 100
        foreach -parallel ($computer in $computers) {

            inlinescript {
                Write-Verbose "$(Get-Date) Processing $using:computer"
                Start-Sleep -Seconds (Get-Random -Minimum 1 -Maximum 5)
                Get-Eventlog -LogName system -EntryType error, Warning -ComputerName $using:computer
            } #inline
        } #foreach
    } #sequence
} #workflow
}

$d = getlogs -pscomputername localhost -ErrorAction SilentlyContinue

这不是 PowerShell 工作流程的目的,尽管我预计很多人都尝试过以这种方式使用它们。该命令在 10:05 完成,尽管如果使用更高的油门限制可能会有所改善。

结论

您可以得出的唯一绝对结论是,您需要进行自己的测试来确定使用 -parallel 对您的命令或项目是否有意义。就我个人而言,我的看法是 -parallel 最适合具有大量可变性的大规模操作。使用 Invoke-Command 编写可扩展的命令可能会得到类似的结果。

$computers = ('srv1', 'srv2', 'win10', 'dom1') * 100
$d = Invoke-Command {
        Get-WinEvent -FilterHashtable @{Logname = "system"; Level = 2, 3 }  
        Start-Sleep -Seconds (Get-Random -Minimum 1 -Maximum 5)
} -throttlelimit 32 -ComputerName $computers

是的,由于必须设置和拆除 PSSession,因此存在开销。对我来说,这在 1 分 56 秒内完成,这仍然是可以接受的性能,而且作为奖励,这不需要 PowerShell 7。

我计划继续尝试此功能,并且很想听听您的使用体验。我相信最终社区会就何时使用它达成共识,但现在我们很高兴能弄清楚这一点!

您需要 登录账户 后才能发表评论

取消回复欢迎 发表评论:

关灯