假设我正在使用get-childitem c:\*.* -recurse
,并且正在使用它。在管道处理它之前,我必须等待整个get-childitem命令完成。诸如select -first 2
之类的异常神奇地停止了先前的命令。无论如何,有一种方法可以提高输出,使其立即写入,而不是吸收大量的ram?我有一个主意是...(我知道这行不通,但可以使这个主意得以传播)
[System.IO.File]::ReadLines("$(dir c:\*.* -recurse)")
我知道这是Windows的事情,因为Linux会在显示数据后立即对其进行处理。但是我知道两个不同的世界。
我最担心的是ram的使用...
这是一个很好的例子
(1..10000000) | where {$_ -like "*543*"}
这花了我的机器大约100秒
其中
(1..10000000).where({$_ -like "*543*"})
只用了25秒。
答案 0 :(得分:4)
在管道处理它之前,我必须等待整个get-childitem命令完成。
否:PowerShell管道的要点是在对象可用时逐个处理对象 ,从而充当保持内存的内存节流不管输入集合的大小如何,都应使用常量。
注意事项:请勿在通过管道发送其输出的命令周围使用(...)
,因为这确实会首先在内存中完全收集该命令的输出。
Cmdlet ,作为PowerShell的本机命令,固有地支持这种一对一的流式传输。
但是,某些cmdlet(例如Sort-Object
和Group-Object
必须首先收集内存中的所有输入 [1] ,作为概念上的必要性(例如,只有比较了所有个项目,您才能生成排序的输出)。谢谢Bacon Bits。
类似地,诸如ConvertTo-Json
之类的cmdlet仅发出一个单个输出对象,它们从前面收集的整个输入中构造了一个对象。
类似地, 外部程序 的stdout输出在行可用时逐行通过 。
您可以将 表达式 封装到& { ... }
中,从而将其转换为流式命令,但这仅在表达式没有已经在内存中建立了完整的对象集合;例如,
& { 1.. 10000000 } | ...
不会给您带来任何好处,但
& { for ($i=0; $i -lt 10000000; ++$i) { $i } } | ...
会。
最终,如果源cmdlet / program / expression本身并未以流式传输方式(如正在生成的对象一个一地)发出输出对象,那么您就不走运了。
但是,实际上缺少的是停止按需停止管道处理 的能力-目前只有Select-Object -First
才能做到-参见我的this answer。
长期存在的feature request on GitHub要求一种机制来按需停止管道。
顺便说一句:使用PSv4 + .Where()
method确实比使用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处理活动对象(而不是静态字符串)的能力将带来重大的序列化/反序列化挑战,因为要使用临时文件。