【问题标题】:No garbage collection while PowerShell pipeline is executingPowerShell 管道正在执行时没有垃圾收集
【发布时间】:2015-10-15 17:31:39
【问题描述】:

更新: PowerShell 5 似乎解决了以下错误。错误仍然存​​在于 3 和 4 中。因此,除非您正在运行 PowerShell 2 或5.


考虑以下代码sn-p:

function Get-DummyData() {
    for ($i = 0; $i -lt 10000000; $i++) {
        "This is freaking huge!! I'm a ninja! More words, yay!"
    }
}

Get-DummyData | Out-Null

这将导致 PowerShell 内存使用量无法控制地增长。在执行了几次Get-DummyData | Out-Null 之后,我看到 PowerShell 内存使用量一直上升到 4 GB。

根据ANTS Memory Profiler,我们在垃圾收集器的终结队列中有很多东西。当我调用[GC]::Collect() 时,内存从 4 GB 变为仅 70 MB。所以严格来说,我们没有内存泄漏。

现在,当我完成一个长期存在的管道操作时,能够致电[GC]::Collect() 对我来说还不够好。我需要在管道操作期间进行垃圾收集。但是,如果我在管道执行时尝试调用 [GC]::Collect()...

function Get-DummyData() {
    for ($i = 0; $i -lt 10000000; $i++) {
        "This is freaking huge!! I'm a ninja! More words, yay!"

        if ($i % 1000000 -eq 0) {
            Write-Host "Prompting a garbage collection..."
            [GC]::Collect()
        }
    }
}

Get-DummyData | Out-Null

...问题仍然存在。内存使用量再次无法控制地增长。我尝试了几种变体,例如添加[GC]::WaitForPendingFinalizers()、Start-Sleep -Seconds 10 等。我尝试更改垃圾收集器latency modes 并强制PowerShell 使用server garbage collection 无济于事。我只是无法让垃圾收集器在管道执行时完成它的工作。

这在 PowerShell 2.0 中根本不是问题。值得注意的是,$null = Get-DummyData 似乎也可以在没有内存问题的情况下工作。所以它似乎与管道有关,而不是我们生成大量字符串这一事实。

如何防止我的内存在长管道期间不受控制地增长?

旁注:

我的 Get-DummyData 函数仅用于演示目的。我的实际问题是我无法使用Get-Content 或Import-Csv 在PowerShell 中读取大文件。不,我不将这些文件的内容存储在变量中。我应该是strictly using the pipeline。 Get-Content .\super-huge-file.txt | Out-Null 产生同样的问题。

【问题讨论】:

  • 内存耗尽部分听起来像是一个错误。您可以通过使用分配、强制转换或属性枚举避免管道/枚举 1000 万个对象,从而显着减少 CPU 时间
  • 我无法用提供的代码 sn-p 重现问题。
  • @RomanKuzmin 你在使用 PowerShell 2.0 吗?
  • 在 V4 和 V5 版本 5.0.10240.16384 上执行第二个示例期间,我看到 > 2 GB 的内存使用量。

标签: powershell memory garbage-collection powershell-3.0


【解决方案1】:

这里有几点需要指出。首先,GC 调用确实在管道中工作。这是一个只调用 GC 的管道脚本:

1..10 | Foreach {[System.GC]::Collect()}

这是脚本运行期间 GC 的性能图:

但是,仅仅因为您调用了 GC,并不意味着私有内存使用量将恢复到您在脚本启动之前的值。 GC 收集只会收集不再使用的内存。如果有对对象的根引用,则它没有资格被收集(释放)。因此,虽然 GC 系统通常不会在 C/C++ 意义上泄漏,但它们可能拥有比它们应有的时间更长地持有对象的内存囤积。

在使用内存分析器查看此内容时,似乎大部分多余的内存都被带有参数绑定信息的字符串副本占用:

这些字符串的根如下所示:

我想知道是否有一些日志记录功能导致 PowerShell 挂在字符串化的表单管道绑定对象上?

顺便说一句,在这种特定情况下,分配给 $null 以忽略输出的内存效率要高得多:

$null = GetDummyData

另外,如果您需要简单地编辑文件,请查看PowerShell Community Extensions 3.2.0 中的Edit-File 命令。只要您不使用 SingleString 开关参数,它就应该是内存高效的。

【讨论】:

  • 我在 Connect 上发现了这个问题。如果您愿意,请在那里投票 - connect.microsoft.com/PowerShell/feedback/details/1599091/…
  • 虽然它不能完全解决我的问题,但我认为这说明了它是一个只有 MS 才能修复的错误。感谢您深入研究它。
  • 没问题。我支持通过管道流式传输数据的好处,而不是将所有内容都存储为变量 - 在某种程度上没有意识到 PowerShell 本质上就是这样做的。
【解决方案2】:

当您执行一些不寻常的事情(例如处理大量文本文件)时,本机 cmdlet 不能完全满足这一点并不少见。就个人而言,我发现在 Powershell 中使用 System.IO.StreamReader 编写大文件会更好:

$SR = New-Object -TypeName System.IO.StreamReader -ArgumentList 'C:\super-huge-file.txt';
while ($line = $SR.ReadLine()) {
    Do-Stuff $line;
}
$SR.Close() | Out-Null;

请注意,您应该在 ArgumentList 中使用绝对路径。对我来说,它似乎总是假设您在具有相对路径的主目录中。

Get-Content 只是将整个对象作为数组读入内存,然后输出。我认为它只是调用 System.IO.File.ReadAllLines()。

我不知道有什么方法可以告诉 Powershell 在完成后立即从管道中丢弃项目,或者函数可能会异步返回项目,因此它会保留顺序。它可能不允许这样做,因为它无法自然地判断该对象以后不会被使用,或者以后的对象不需要引用较早的对象。

Powershell 的另一个好处是您也可以经常adopt the C# answers。我从未尝试过File.ReadLines,但这看起来也很容易使用。

【讨论】:

  • 即使使用 StreamReader 方法,您通过管道推送字符串的事实也会导致问题。另外,我不认为 Get-Content 返回一个简单的字符串数组。我过去曾在 PowerShell 2.0 中使用它来处理数百兆字节,而内存使用量可以忽略不计。
  • @Phil StreamReader 方法的关键是您根本没有使用管道。您正在逐行读取文件,而不是读取整个文件并通过管道传输内容。你在我有Do-Stuff $line;的地方做你需要的一切。问题是您不能一次访问两条线路,并且性能可能会更差,因为您的 IO 可能会出现瓶颈,但作为回报,您基本上不使用内存。然而,谷歌搜索会发现许多人在使用 Get-Content 时存在记忆问题。 Get-Content | [...] 与 $x = Get-Content 的内存使用不同,这不清楚
猜你喜欢
  • 2017-05-07
  • 1970-01-01
  • 2016-10-12
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多