首页 > 解决方案 > 有没有办法从大命令中更快地流出数据?

问题描述

假设我正在使用get-childitem c:\*.* -recurse并且正在使用它。在管道处理它之前,我必须等待整个 get-childitem 命令完成。有一些例外,例如select -first 2神奇地停止前一个命令。无论如何,有没有办法提高输出,所以它立即写入而不是吸收大量的内存?我的一个想法是......(我知道这行不通,但它让这个想法得以传达)

[System.IO.File]::ReadLines("$(dir c:\*.* -recurse)")

我知道这是 Windows 的事情,因为 Linux 会在数据出现后立即处理它。但我知道,两个不同的世界。

我最担心的是内存使用...

这是一个很好的例子

(1..10000000) | where {$_ -like "*543*"}

这需要我的机器大约 100 秒

在哪里

(1..10000000).where({$_ -like "*543*"})

只用了 25 秒。

标签: performancepowershellpipelinememory-efficient

解决方案


在管道处理它之前,我必须等待整个 get-childitem 命令完成。

否:PowerShell 管道的重点是在对象可用时一个一个地处理它们,从而充当内存节流阀,使内存使用量保持不变,而与输入集合的大小无关。

  • 警告:不要(...)在通过管道发送其输出的命令周围使用,因为这确实会首先在内存中完整收集该命令的输出。

  • Cmdlet作为 PowerShell 的本机命令,天生就支持这种一对一的流式传输。

    • 但是,某些 cmdlet,例如Sort-ObjectandGroup-Object 必须首先收集内存中的所有输入[1],作为概念上的必要性(例如,在比较所有项目之前,您无法生成排序输出)。谢谢,培根片

    • ConvertTo-Json类似地,仅发出单个输出对象的cmdlet,例如,从预先收集的全部输入构造该对象。

  • 同样,来自外部程序的stdout 输出逐行传递,因为这些行变得可用。

  • 您可以通过将表达式包含在 中来将其转换为流式命令& { ... },但这仅在表达式尚未在内存中构建完整的对象集合时才有用;例如,
    & { 1.. 10000000 } | ...不会为您带来任何好处,但
    & { for ($i=0; $i -lt 10000000; ++$i) { $i } } | ...会。

  • 最终,如果源 cmdlet / 程序 / 表达式本身不以流式方式发出输出对象(一个接一个,因为它们正在生成),那么你就不走运了。

但是,确实缺少的是按需停止管道处理的能力-目前只能Select-Object -First这样做-请参阅我的这个答案GitHub 上
有一个长期存在的功能请求,要求提供一种机制来按需停止管道。


顺便说一句:使用 PSv4+.Where() 方法确实比使用Where-Object cmdlet(其内置别名为where)更快,但.Where()总是要求它操作的集合事先已全部加载到内存中。

但是,该.Where()方法确实有能力通过'First'作为第二个参数传递来停止处理剩余的项目,该参数在第一次匹配后停止;'First'[System.Management.Automation.WhereOperatorSelectionMode];的一个实例 将 的性能与 的性能进行
(1..1e6).Where({$_ -eq 10})比较
(1..1e6).Where({$_ -eq 10}, 'First')


[1] 例如,PowerShell 不像Unix实用程序那样使用临时文件来缓解内存压力;sort我的猜测是,这样做在 PowerShell 中并不是一个真正的选择:PowerShell 处理活动对象(而不是静态字符串)的能力将带来重大的序列化/反序列化挑战,这是要使用的临时文件。


推荐阅读