【问题标题】:PS get-content high memory usage - Is there a more efficient way to filter a file?PS get-content 内存使用率高 - 有没有更有效的方法来过滤文件?
【发布时间】:2016-08-10 09:59:10
【问题描述】:

我正在使用 get-content 读取一个较大的文件 (252 MB),但是当我使用 get-content 读取它时,powershell 进程继续消耗近 10 GB 的内存。这是正常行为吗?

该数组只有近 600 万个项目。它似乎与正在使用的内存量不符。

也许我只是以完全错误的方式解决这个问题。

我想将匹配字符串的行和后续行写入一个新的文本文件。

$mytext = get-content $inpath
$search = "*tacos*"
$myindex = 0..($mytext.count - 1) | Where {$mytext[$_] -like $search}
$outtext = @()
foreach ($i in $myindex){
    $outtext = $outtext + $mytext[$i] + $mytext[$i+1]
    }
$outtext | out-file -filepath $outpath

性能测试结果

我在这里根据不同的答案对不同的脚本进行了性能示例。

我的原始脚本

(对写出的行数高度敏感)

  • 10k 行 - 1.8 秒
  • 10 万行 - 38 秒
  • 100k 行 - 21s(搜索字符串很少出现时)
  • 5000k 行 - 太长无法测量(数小时后中止)

不带 Get-Content 的 Select-String(改编自 whatever

Select-String -path $inpath -pattern $search -Context 0,1 -SimpleMatch | Out-File $outpath

  • 10k 行 - 1.2s
  • 100k 行 - 4s
  • 1000k 行 - 107s

如果输入增加 10 倍,注意处理速度只会增加约 4 倍。您尝试一次处理的数据越多,该解决方案相对于其他解决方案就越好。

消除数组大小调整(来自 Mathias)

  • 10k 行 - 2.0s
  • 10 万行 - 25 秒
  • 1000k 行 - 1533s(使用 1.7GB 内存,与在 1000k 行上在脚本之外运行 gc 相同)

使用管道(来自 Chris Dent)

  • 10 万行 - 26 秒

【问题讨论】:

  • 在您的示例中没有一个 Get-Content 实例?

标签: powershell


【解决方案1】:

进程继续消耗近 10 GB 的内存。 [...] 该数组仅包含 600 万个项目。它似乎与正在使用的内存量不符。

Get-Content 对 600 万行的文件会产生 600 万个字符串对象 - 分配字符串对象不仅仅是为字符本身分配内存,还包括对象头和额外开销。

不过,这仅占您所见内容的 5-10% - 真正的问题是这种结构:

$outtext = @() # this
foreach ($i in $myindex){
    $outtext = $outtext + $mytext[$i] + $mytext[$i+1] # and this
}

每次像这样重新分配数组的值时,都必须调整底层数组的大小,导致 .NET 将内容复制到一个新数组

改成:

$outtext = foreach ($i in $myindex){
    $mytext[$i],$mytext[$i+1]
}

【讨论】:

  • 如果您在下一行重新分配变量,为什么需要这个 $outtext = @()
  • @PetSerAl 我没有,我的胖手指根本不擅长复制粘贴
  • 这将我的 10k 行文件的执行时间缩短了一半。 710ms 到 349。
  • 并不惊讶 :-) 内存消耗怎么样?
【解决方案2】:

另一个选项是 Select-String:

$search = "tacos"
Get-Content $inpath | Select-String $search -Context 0,1 | Out-File $OutputFile -Append

但是,这会产生稍微改变的输出:

match
following line

会变成

> match
  following line

如果您想要文件中的确切行:

Get-Content $inpath | Select-String $search -Context 0,1 | foreach {$_.Line | Out-File $OutputFile -Append ; $_.Context.Postcontext |  Out-File $OutputFile -Append}

顺便说一句:一旦文件变得非常大,Get-Content 就会变得有点慢。一旦发生这种情况,这样做可能会更好:

$TMPVar = Get-Content $inpath -Readcount 0
$TMPVar | Select-String....

这将使 Get-Content 一次读取整个文件,而不是逐行读取,这比将其直接通过管道传输到下一个 cmdlet 更快但需要更多内存。

【讨论】:

  • 原始示例中的 get-content 命令导致了疯狂的内存消耗。它使用近 10GB 来读取 260MB 的文件。我单独运行它,仍然得到相同的内存使用情况。似乎我需要一个完全避免获取内容的解决方案。
  • Select-String 可以直接从文件中读取:stackoverflow.com/questions/13916650/…Select-String -path $inpath -pattern $search -Context 0,1 -SimpleMatch | Out-File $outpath
  • 10gb 似乎真的太多了,用大约 220MB 的文本文件做了一个快速基准测试(只读取文件和选择字符串,不将其写入磁盘): $var = get-content; $变量 |选择字符串需要 200 秒和 3GB 的内存,获取内容 |选择字符串 37s 70mb, $var = get-content -readcount 0 ; $变量 | select-string 19s 430mb select-string -inputobject (get-item) 3s 120mb (!) 所以像你在上一条评论中建议的那样使用文件作为 inputobject 的 select-string 似乎是迄今为止最好的方法。我没有测试这个主题中的其他变体,也许它们更好......
  • 我今天再次使用 1M 行文件(大约 90M)进行了测试,在该文件上运行 get-content 会消耗 1.7GB 的 RAM。我还在上面添加了一些测试结果。文件上的 Select-String 从字面上将所有其他方法从水中吹走!
  • 感谢发布速度,很有趣。如果您真的在寻找最佳性能,我发现您可能还可以做一件事:Add-Content -Path $inpath -Value (Select-String -path $inpath -pattern $search -Context 0,1 -SimpleMatch) 似乎比“| Out-File”好很多:)(从 300 万个匹配项中导出 20000 个匹配项时,大约 4 秒 vs 7 秒行文件)。可能已经超过收益递减点,但我很好奇......
【解决方案3】:

管道是您的朋友。除了延长索引过程并增加更多内存之外,您的索引过程没有任何好处。

这将获取您正在搜索的行,以及您需要的一行上下文(来自示例)。除了与您的搜索匹配的项目加上那一行之外,没有任何内容加载到内存中。

$getNext = $false
$outtext = Get-Content $inPath | ForEach-Object {
    if ($_ -like $search) {
        $_
        $getNext = $true
    }
    elseif ($getNext) { #reads the following line on next iteration
        $_
        $getNext = $false
    }
}

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2010-10-26
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多