Multithreading Foreach对象比Foreach-parallel更快?

Multithreading Foreach对象比Foreach-parallel更快?,multithreading,powershell,foreach,parallel-processing,Multithreading,Powershell,Foreach,Parallel Processing,我刚刚开始使用powershell,我想知道为什么我的并行srcipt比我的普通foreach对象脚本慢 我的普通foreachobject脚本: function Get-ADUsers { #get all users in nested groups } function Get-NestedGroupUsers { param ( [Parameter(Mandatory = $true)][String]$FileName, [Param

我刚刚开始使用powershell,我想知道为什么我的并行srcipt比我的普通foreach对象脚本慢

我的普通foreachobject脚本:

function Get-ADUsers {  #get all users in nested groups }

 function Get-NestedGroupUsers {
    param ( 
        [Parameter(Mandatory = $true)][String]$FileName,
        [Parameter(Mandatory = $true)][String]$searchFileURL
    )
    $storageHolder = @()
    # $storageHolder | Export-Csv -Path "C:\Users\demandx\Desktop\AD User Lists\$FileName.csv" -NoTypeInformation -Force 
    $groupList = Get-Content $searchFileURL 
    $groupList |  ForEach-Object { 
        $allusers = Get-ADUsers -GroupName $_
        $storageHolder += $allusers  
       
    }
    $storageHolder | select ParentGroup, Name, EmployeeNumber, Enabled, LastLogonDate, PasswordLastSet  |Export-Csv -Path "C:\Users\demandx\Desktop\$FileName.csv" -NoTypeInformation -Force
}
我的foreach-parallel脚本(我将函数存储在psm1中,然后在这里导入。)

脚本或my get adusers(获取嵌套组中的所有用户)

平行结果

-------------------------------------------
Days              : 0
Hours             : 0
Minutes           : 1
Seconds           : 2
Milliseconds      : 283
Ticks             : 622833415
TotalDays         : 0.000720872008101852
TotalHours        : 0.0173009281944444
TotalMinutes      : 1.03805569166667
TotalSeconds      : 62.2833415
TotalMilliseconds : 62283.3415
非平行结果

-------------------------------------------
Days              : 0
Hours             : 0
Minutes           : 0
Seconds           : 35
Milliseconds      : 322
Ticks             : 353221537
TotalDays         : 0.00040882122337963
TotalHours        : 0.00981170936111111
TotalMinutes      : 0.588702561666667
TotalSeconds      : 35.3221537
TotalMilliseconds : 35322.1537
TLDR:

原因有三:

  • 要充分利用
    ForEach Object-Parallel
    性能,脚本块的处理时间需要明显大于设置线程和环境的时间
  • 导入模块将引入开销
  • 这两个因素各自都很小,但乘以1000或更大的数字,它们就会变大

  • ForEach对象-并行
    运行方式与正常的
    ForEach对象
    运行方式非常不同

    首先,一个普通的
    ForEach对象运行在您当前的PowerShell线程中,可以访问所有变量、加载的内存和管道。这对于我们运行的所有作业中的98%都很好,1秒的执行时间也可以。在2%的情况下,我们有一个超级CPU密集型进程,它可以最大限度地利用单个CPU核心并永远运行,或者我们需要等待其他执行时的响应(例如API请求),那么我们需要考虑的是
    -Parallel

    并行执行背后的理念是利用您全新的AMD Ryzen™ 螺纹裂土器™ 3990X,64核/128线程,并将进程拆分为单独的“作业”,可同时跨多个CPU核和多个线程运行。这可能会将您的速度提高几个数量级,例如,可能快128倍

    为了实现这一点,
    ForEach Object-Parallel
    为您执行的每个脚本块创建一个新的“作业”,并开始将作业分散到CPU内核上执行。当您有长时间运行的CPU限制的进程时,这是非常好的,但是当您有非常短和小的作业时,您会遇到并行执行的关键,在并行执行中,设置比实际执行花费更多的时间
    ForEach Object-Parallel
    必须为您运行的每个“作业”完全设置环境,例如,它必须为要运行的每个作业启动多个新线程和多个新PowerShell实例

    为了说明所需的设置时间,如果我们向当前线程写入一次“Hello World”,则需要1毫秒:

    PS C:\> Measure-Command { Write-Host "Hello World" }
    Hello World
    
    Seconds           : 0
    Milliseconds      : 1
    TotalMilliseconds : 1.9798
    
    并行运行一个“Hello World”需要26毫秒:

    PS C:\> Measure-Command { 1 | ForEach-Object -Parallel { Write-Host "Hello World" } }
    Hello World
    
    Seconds           : 0
    Milliseconds      : 26
    TotalMilliseconds : 26.052
    
    这意味着它花了大约25毫秒来构建一个新线程,并设置环境和1毫秒的实际工作

    在当前运行的线程上写入100次大约需要83毫秒:

    PS C:\> Measure-Command { 1..100 | ForEach-Object { Write-Host "Hello World" } }
    Hello World
    ...
    Hello World
    Hello World
    
    Seconds           : 0
    Milliseconds      : 83
    TotalMilliseconds : 83.1846
    
    使用
    -ThrottleLimit 5运行
    -Parallel
    需要294ms:

    PS C:\> Measure-Command { 1..100 | ForEach-Object -Parallel { Write-Host "Hello World" }  -ThrottleLimit 5 }
    Hello World
    ...
    Hello World
    Hello World
    
    Seconds           : 0
    Milliseconds      : 294
    TotalMilliseconds : 294.3205
    
    这说明并行运行对于微小的单个操作是多么有害。但另一方面,如果你有需要1秒运行的东西,你可以开始看看它是如何更好地工作的:

    e、 g.运行5个进程,每个进程耗时1秒。首先在单个线程上:

    PS C:\> Measure-Command { 1..5 | ForEach-Object { Start-Sleep -Seconds 1 } }
    
    Seconds           : 5
    Milliseconds      : 46
    TotalSeconds      : 5.046348
    TotalMilliseconds : 5046.348
    
    正如预期的那样,这只需要5秒多。现在,同时:

    PS C:\> Measure-Command { 1..5 | ForEach-Object -Parallel { Start-Sleep -Seconds 1 } -ThrottleLimit 5 }
    
    Seconds           : 1
    Milliseconds      : 73
    TotalSeconds      : 1.0732423
    TotalMilliseconds : 1073.2423
    
    它只需一秒钟就完成了。如果处理时间远远超过设置时间,则
    -Parallel
    非常有用

    此外,在您的情况下,不仅您有额外的设置时间开销,而且加载一个模块(需要设置新环境)会为
    ForEach Object-Parallel
    版本增加更多的时间

    例如,让我们将模块
    AzureAD
    导入到我们的
    ForEach对象中
    脚本5次:

    PS C:\> Measure-Command { 1..5 | ForEach-Object { Import-Module AzureAD } }
    
    Seconds           : 0
    Milliseconds      : 18
    TotalSeconds      : 0.0185406
    TotalMilliseconds : 18.5406
    
    现在使用ForEach对象-Parallel的

    PS C:\> Measure-Command { 1..5 | ForEach-Object -Parallel { Import-Module AzureAD } -ThrottleLimit 5 }
    
    Seconds           : 0
    Milliseconds      : 125
    TotalSeconds      : 0.1256923
    TotalMilliseconds : 125.6923
    

    我们可以看到有一个显著的区别,因为它必须加载模块5次,而不是在线程中只加载一次,然后注意到它仍然被加载,而不是重新加载。

    有些进程根本不适合并行运行。有些并行化方法有很大的开销。您似乎遇到了一个或多个这样的问题。导入模块会有开销。您在每个作用域中执行此操作,而不是在单个线程中执行一次,除非您对已加载的数据执行大量CPU处理,否则多线程的开销通常会较慢。@DougMaurer不幸的是,此开销无法避免(目前),请参阅,非常感谢!这本书对我理解事物有很大帮助。
    PS C:\> Measure-Command { 1..5 | ForEach-Object { Import-Module AzureAD } }
    
    Seconds           : 0
    Milliseconds      : 18
    TotalSeconds      : 0.0185406
    TotalMilliseconds : 18.5406
    
    PS C:\> Measure-Command { 1..5 | ForEach-Object -Parallel { Import-Module AzureAD } -ThrottleLimit 5 }
    
    Seconds           : 0
    Milliseconds      : 125
    TotalSeconds      : 0.1256923
    TotalMilliseconds : 125.6923