【问题标题】:Any efficiency difference between using pipeline and directly using InputObject parameter?使用管道和直接使用 InputObject 参数之间有什么效率差异吗?
【发布时间】:2018-10-18 22:21:25
【问题描述】:

这里是一个例子,我有一个名为“s1”的远程服务器,我想杀死里面的calc、notepad、winword的进程。

我可能有两种方法可以做到这一点,

  1. 使用管道

Get-Process -computername s1 -name "calc", "notepad", "winword" | Stop-Process

  1. 使用 InputObject 参数

$processes = Get-Process -computername s1 -name "calc", "notepad", "winword" Stop-Process -InputObject $processes

IMO,我认为第二种方式比第一种方式要好得多。

据说PowerShell中的管道实际上是一个一个的将对象传递给下面的Cmdlet。在这种情况下,Stop-Process 需要与远程计算机“s1”通信多次并一个一个地杀死这些进程。

相比之下,第二种方式,我猜Stop-Process只会与远程计算机“s1”通信一次,并一次性完成操作。

我理解正确吗?

谢谢

马丁

【问题讨论】:

  • 这也让我很困惑。适当的用法是始终通过管道输入命令。从我能收集到的。 -inputobject 会将所有项目组合为一个,从而混淆系统。一些 cmdlet 可能会更好地处理它。我从来没有发现需要使用它。它还为您的 cmd 添加了一个步骤。将输出放入变量中。然后将其放入您的命令中,而不是将其直接放入命令中(至少在大多数情况下),变量也是快照。如果您尝试通过停止进程关闭一个老化的变量,您可能会杀死错误的进程。等等……
  • 停止进程也可以只取一个名称参数,节省更多步骤。 Stop-Process -computername s1 -name "calc", "notepad", "winword"
  • 另一个选项是调用命令,您可以将命令作为一个发送,它会立即执行整个操作。不过要小心。默认情况下,invoke-command 在您使用它的任何机器上构建配置文件。所以通过 pssessionoptions 更改默认值
  • @RobertCotterman: Stop-Process 没有-ComputerName 参数。
  • 我在答案中添加了更一般的-InputObject 与管道信息;抄送@RobertCotterman。

标签: powershell


【解决方案1】:

至于远程处理方面

您应该选择完全不同的方法:远程运行整个管道,使用Invoke-Command

Invoke-Command -ComputerName s1 { 
  Get-Process -Name 'calc', 'notepad', 'winword' | Stop-Process
}

但请注意,Invoke-Command (PSv3+) 需要在目标计算机上配置 PowerShell 远程处理(请参阅 Get-Help about_Remote_FAQ),而 Get-Process cmdlet 使用不同的过时形式的远程处理。

事实上,当我在两台 v5.1 机器之间尝试你的方法时,本地运行的 Stop-Process 命令试图对远程进程对象进行操作失败,并出现以下错误:

Cannot stop process "<name>" because of the following error:
Feature is not supported for remote machines.

一般来说,最好的方法是远程执行尽可能多的处理,并且只将结果传输到本地机器


至于更管道输入与通过-InputObject 输入的一般方面

使用 -InputObject 传递集合 (Get-Foo -InputObject $collection) 肯定比通过管道发送集合 ($collection | Get-Foo) 更快,但请注意它需要整个输入集合作为一个整体加载到内存中,预先,这可能会抵消管道的一个关键好处:内存节流。

请注意,使用-InputObject 通常不是管道输入的可行替代方案,因为许多 cmdlet不枚举您传递的集合到-InputObject(比较1, 2 | ForEach-Object { "[$_]" }ForEach-Object { "[$_]" } -InputObject 1, 2);通常,这是偶然发生的,-InputObject 声明不是 数组,但有时这是设计使然:Get-Member cmdlet 故意不枚举传递给 -InputObject 的集合,因为然后它检查 collection 的 类型,而不是其元素的类型。有关背景信息,请参阅this GitHub issue

另请注意,如果还有更多的管道段 (Get-Foo -InputObject ... | ...),则流式处理(一对一处理)再次发生输出

您可以使用foreach 语句 (foreach ($elem in $collection) { ... }) 代替管道来加速逐项处理,但这只是有效的如果您可以避免循环体中的 cmdlet 调用
然而,与-InputObject 一样,这需要将整个输入集合作为一个整体加载到内存中,预先


使用Time-Command 与 10,000 个输入对象进行性能比较,平均运行 100 次:

$collection = 1..10000
Time-Command -Count 100 { $collection | Write-Output }, 
                        { Write-Output -InputObject $collection },
                        { foreach ($o in $collection) { $o } }

示例时序(Windows 10 上的 Windows PowerShell 5.1,单核 VM):

Command                               Secs (100-run avg.) TimeSpan         Factor
-------                               ------------------- --------         ------
foreach ($o in $collection) { $o }    0.010               00:00:00.0103421 1.00
Write-Output -InputObject $collection 0.015               00:00:00.0152200 1.47
$collection | Write-Output            0.108               00:00:00.1076183 10.41
  • foreach 最快(注意 implicit 输出在循环体中是如何依赖的:如果使用了 Write-Output 调用,这将是迄今为止最慢的解决方案),-InputObject业绩不甘落后;有趣的是,这些角色在 PowerShell Core 中似乎颠倒了
  • 通过管道提供输入的速度大约慢了 10 倍。

但是,请注意,这些因素会因输入集合的大小(可能还有您的硬件)而异。

【讨论】:

  • 这有点误导。最后完成你的任务。 Foreach 肯定更快。但这是因为调用变量所需的时间最短。如果您使用 foreach 来迭代需要一段时间才能启动的 cmdlet,则情况可能会更糟。随着 cmdlet 的启动、运行和退出。而不仅仅是循环遍历它的自我。一个很好的例子是 dsinternals cmdlet(不是 std)get-addbaccount。我通过一个循环运行了 60,000 个帐户。而且花了72小时。对于相同数量的帐户,只需要求它一次转储每个帐户大约需要 5 分钟
  • @RobertCotterman:请注意答案中的以下部分:“仅当您可以避免循环体中的 cmdlet 调用时才有效”和“注意循环体中如何依赖隐式输出:如果使用了 Write-Output 调用,这将是迄今为止最慢的解决方案”。那么,哪一部分具有误导性?
  • 考虑到他的目标是调用一个cmdlet。但我想我让我疲惫的大脑读得不够。
猜你喜欢
  • 1970-01-01
  • 2021-09-18
  • 1970-01-01
  • 2010-10-25
  • 1970-01-01
  • 2013-12-27
  • 1970-01-01
  • 2011-02-04
  • 1970-01-01
相关资源
最近更新 更多