有没有一种方法可以从大型命令中更快地流式传输数据?

时间:2018-08-15 02:35:25

标签: performance powershell pipeline memory-efficient

假设我正在使用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秒。

1 个答案:

答案 0 :(得分:4)

  

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

否:PowerShell管道的要点是在对象可用时逐个处理对象 ,从而充当保持内存的内存节流不管输入集合的大小如何,都应使用常量

  • 注意事项:请勿在通过管道发送其输出的命令周围使用(...),因为这确实会首先在内存中完全收集该命令的输出。

  • Cmdlet ,作为PowerShell的本机命令,固有地支持这种一对一的流式传输。

    • 但是,某些cmdlet(例如Sort-ObjectGroup-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处理活动对象(而不是静态字符串)的能力将带来重大的序列化/反序列化挑战,因为要使用临时文件。