【问题标题】:Powershell extraction memory consumingPowershell提取内存消耗
【发布时间】:2019-10-10 08:19:15
【问题描述】:

我从事过一个小项目,该项目是从文件服务器中提取一些信息。为了执行该项目,我创建了一个脚本,将所有信息输出到 .csv 文件中。问题是 Powershell 在此过程中耗尽了我所有计算机的 RAM,因为有数百 Gb 的数据需要解析。

下面是我的脚本。

$folder = Get-ChildItem -Recurse 'Complete_Path' | select FullName, @{Name="Owner";Expression={(Get-Acl $_.FullName).Owner}}, CreationTime, LastWriteTime, LastAccessTime, PSIsContainer | sort FullName
$output = @()

$folder | foreach {

$type =

if ($_.PSIsContainer -eq "True") {


    Write-Output "Folder"

        }
else {


    Write-Output "File"

}


$size =

if ($_.PSIsContainer -eq "True") {

   Get-ChildItem -Recurse $_.FullName | measure -Property Length -Sum -ErrorAction SilentlyContinue | select -ExpandProperty Sum


        }
else {


    Get-Item $_.FullName | measure -Property Length -Sum -ErrorAction SilentlyContinue | select -ExpandProperty Sum

}


$hash = @{


FullName = $_.FullName
Owner = $_.Owner
CreationTime = $_.CreationTime
LastWriteTime = $_.LastWriteTime
LastAccessTime = $_.LastAccessTime
Type = $type
'Size in MB' = [math]::Round($($size/1Mb),2)

}

$output += New-Object PSObject -Property $hash
}

$output | select FullName, Owner, CreationTime, LastWriteTime, LastAccessTime, Type, 'Size in MB' | Export-Csv C:\myDOCS.csv -Delimiter ";" -NoTypeInformation -Encoding UTF8

你们知道我怎样才能更快地完成工作并减少内存消耗吗?提取可能需要几天时间。

提前谢谢你。

【问题讨论】:

  • [1] 在数组上使用+=复制到一个新的、大一项的数组,添加新项,然后删除旧数组。这将要求您在进行添加时在 RAM 中有两个数组副本。所以......不要那样做。 [grin] 使用具有.Add() 方法的集合类型,就像generic.list 一样。 ///// 下一条评论中的下一条 ...
  • [接上一条评论 ...] ///// [2] 为了节省 RAM,使用管道以便只有当前工作项在 RAM 中。这里的权衡是速度较慢,内存较少。 ///// [3] 如果这不适合您,您将导出行移动到循环中并立即导出每个项目,而不是将其全部保存在 RAM 中。这也会大大减慢速度,但总体上会使用更少的 RAM。
  • 感谢您提供所有这些信息@Lee_Dailey。然而,我对这一切有点迷茫。我应该在哪里应用这些更改?
  • 我看到其他人已经非常详细地回答了你,所以我只建议你使用他们的代码 - 希望你能回忆起我所说的 why . [咧嘴一笑]

标签: powershell csv scripting extract ram


【解决方案1】:
  • 将您的 Powershell 数组 $output=@() 替换为 .Net PSObject 列表 $output = [System.Collections.Generic.List[psobject]]::new() 并使用该对象的 .Add 方法添加您的项目。

    对于小列表,您不会注意到,但使用 Powershell 数组和 += 运算符是一个很大的性能下降。每次执行 += 时,数组都会重新创建一个项目。

  • 在初始 Get-ChildItem 语句中包含长度。稍后,您无需一直再次通过 Get-ChildItem 即可测量总和

  • 管道在内存上运行良好,但总体上较慢。当性能成为问题时,我倾向于不使用管道。

类似的东西应该已经明显更快了

$folder = Get-ChildItem -Recurse "$($env:USERPROFILE)\Downloads" | select FullName, @{Name = "Owner"; Expression = { (Get-Acl $_.FullName).Owner } }, CreationTime, LastWriteTime, LastAccessTime, PSIsContainer, Length | sort FullName
$output = [System.Collections.Generic.List[psobject]]::new()

foreach ($Item in $folder) {
    if ($Item.PSIsContainer) {
        $Type = 'Folder'
        $size = $folder.Where( { $_.FullName -like $item.FullName }).FullName | measure -Property Length -Sum -ErrorAction SilentlyContinue | select -ExpandProperty Sum
    }
    else {
        $Type = 'File'
        $size = $Item.Length
    }
    $size = [math]::Round($($size / 1Mb), 2)

    $hash = @{
        FullName       = $Item.FullName
        Owner          = $Item.Owner
        CreationTime   = $Item.CreationTime
        LastWriteTime  = $Item.LastWriteTime
        LastAccessTime = $Item.LastAccessTime
        Type           = $Type
        'Size in MB'   = $size
    }
    [void]($output.Add((New-Object PSObject -Property $hash)))
}


$output | select FullName, Owner, CreationTime, LastWriteTime, LastAccessTime, Type, 'Size in MB' | Export-Csv C:\myDOCS.csv -Delimiter ";" -NoTypeInformation -Encoding UTF8
  • 您仍然可以改进大小计算,因此首先计算最深的文件夹大小,然后父文件夹可以获取该值并对子文件夹求和,而不是重新计算文件

  • 另一个想法是不要立即执行 Get-ACL(我怀疑这个执行速度很慢)并获取您的项目,完成其余的工作,然后并行化 Get-ACL,以便您可以获取值一些并行线程,并将值添加到列表中。

考虑以小批量测试您的代码,并使用 Measure-Command 确定代码中最慢的操作在哪里。

我建议您查看有关该主题的一些更高级的主题。 这是一篇很好的文章,可以帮助您入门:Slow Code: Top 5 ways to make your Powershell scripts run faster

【讨论】:

  • 最终,如果您对 .Net 有一定了解,那么构建一个小型 DLL 来执行此类非常密集的工作可能会非常有益。使用Add-Type,您可以将 dll 导入 Powershell,并且仍然可以从 Powershell 执行您想要的所有操作,但核心循环除外。但如果没有,根据我的回答,您可以采取一些步骤来使您的纯 powershell 代码更快并减少内存占用。
  • Appending to arrays: $output.Add((New-Object PSObject -Property $hash))$output += New-Object PSObject -Property $hash 是同一种错误方法。检查this @js2010's answer 以获取正确的方法。
  • 非常感谢!明天我会先试一试。我会告诉大家的。
  • @JosefZ 附加到数组,是的。我正在使用 .net 集合(我引用的文章提到 ArrayList 但不推荐使用 ArrayList,因此我选择 System.collections.Generic.list 作为集合类型。
  • 大家好!我刚刚有机会对您的脚本进行测试。我用一个 56.5 Gb 的目录提供了所有这些文件,并测量了执行运行时间,以查看获取最终 csv 文件需要多长时间。所以,我的初始脚本使用了 00:02:06、js2010 00:01:57 和 SagePourpre 00:02:32。从现在开始我一定会用管道!谢谢大家
【解决方案2】:

将整个事情放在一个管道中会更好吗?

Get-ChildItem -Recurse |
select FullName, @{Name="Owner";Expression={(Get-Acl $_.FullName).Owner}},
CreationTime, LastWriteTime, LastAccessTime, PSIsContainer | sort FullName |

foreach {

  $type =
  if ($_.PSIsContainer -eq "True") {
      Write-Output "Folder"
  }
  else {
      Write-Output "File"
  }

  $size =
  if ($_.PSIsContainer -eq "True") {
     Get-ChildItem -Recurse $_.FullName | 
       measure -Property Length -Sum -ErrorAction SilentlyContinue |
       select -ExpandProperty Sum
  }
  else {
      Get-Item $_.FullName | 
        measure -Property Length -Sum -ErrorAction SilentlyContinue |
        select -ExpandProperty Sum
  }

  $hash = @{
    FullName = $_.FullName
    Owner = $_.Owner
    CreationTime = $_.CreationTime
    LastWriteTime = $_.LastWriteTime
    LastAccessTime = $_.LastAccessTime
    Type = $type
    'Size in MB' = [math]::Round($($size/1Mb),2)
  }

  New-Object PSObject -Property $hash
} | select FullName, Owner, CreationTime, LastWriteTime, LastAccessTime,
Type, 'Size in MB' | 
Export-Csv myDOCS.csv -Delimiter ";" -NoTypeInformation -Encoding UTF8

【讨论】:

  • 您不需要计算辅助$hash 对象。只需返回[PSCustomObject]@{ … }
猜你喜欢
  • 1970-01-01
  • 2017-02-09
  • 1970-01-01
  • 2015-01-16
  • 2010-10-12
  • 1970-01-01
  • 2011-10-03
  • 2012-11-24
  • 2013-10-08
相关资源
最近更新 更多