【问题标题】:Can I make this script even faster?我可以让这个脚本更快吗?
【发布时间】:2017-11-06 18:20:42
【问题描述】:

我为实习编写了一个简单的脚本,它在提供的目录中搜索并删除任何早于指定天数的文件。我今天所有的空闲时间都在试图收紧它。到目前为止,这是我所得到的:

function delOld($dir, $numDays){
    $timespan = new-timespan -days $numDays
    $curTime = get-date
    get-childItem $dir -Recurse -file | 
    where-object {(($curTime)-($_.LastWriteTime)) -gt $timespan} | 
    remove-Item -whatif
}

这里是一个函数调用的例子:

delOld -dir "C:\Users\me\Desktop\psproject" -numDays 5

抱歉阅读困难,我发现将操作压缩到一行比每次迭代都将它们重新分配给易读的变量更有效。 remove-item 目前是 whatif'd 用于测试目的。我知道在这一点上,我可能无法加快速度,但是,我在超过 TB 的文件上运行它,所以每个操作都很重要。

提前感谢您提供的任何建议!

【问题讨论】:

  • 这是我眼中的最快速度。我真的不知道除了设计它来解雇工作之外,它还能更快吗?但是这样重新设计无论如何都会抵消速度的提高
  • 你试过日志解析器吗?
  • 99% 的时间都花在Get-ChildItem 读取物理磁盘上,因此如果有任何显着加快速度的方法,它将使用Everything's API 直接读取磁盘的 MFT(时间/日期索引应该启用)搜索查询可能只需要几秒钟!
  • 追秒:优化文件枚举youtube.com/…

标签: windows powershell powershell-4.0


【解决方案1】:

许多 PowerShell cmdlet 比它们的 .NET 等效项慢。例如,您可以改为调用[System.IO.File]::Delete($_.FullName),看看是否存在性能差异。 Get-ChildItem => [System.IO.Directory]::GetFiles(...) 也是如此。

为此,我会编写一个小脚本来创建两个临时文件夹,其中每个文件夹包含 100,000 个空测试文件。然后调用包裹在[System.Diagnostics.StopWatch]中的每个版本的函数。

一些示例代码:

$stopwatch = New-Object 'System.Diagnostics.StopWatch'
$stopwatch.Start()

Remove-OldItems1 ...

$stopwatch.Stop()
Write-Host $stopwatch.ElapsedMilliseconds

$stopwatch.Reset()
$stopwatch.Start()

Remove-OldItems2 ...

$stopwatch.Stop()
Write-Host $stopwatch.ElapsedMilliseconds

PowerShell 的更多亮点:在 Powershell 窗口中运行 Get-Verb,您可以看到已批准的动词列表。建议将 PowerShell 中的函数命名为 Verb-Noun,因此 Remove-OldItems 之类的名称很合适。

【讨论】:

  • 等效的 .net 方法是否更快完全取决于使用情况。许多 PowerShell cmdlet 被编写为接受管道输入并对多个项目进行操作,但人们改为通过管道传输到 ForEach-Object,然后在每个单独项目的块内调用 cmdlet。这种方法的问题在于,cmdlet 中的设置/拆卸代码会为每个项目运行,而如果项目是通过管道传输的,它只会运行一次。这只是如何真正放慢速度和 cmdlet 的一个示例,但这一切都取决于上下文,因此测试很好。
  • 这个答案没有提到非 SSD 磁盘速度(随机查找 + 读取)比 PS cmdlet 与 .NET 方法之间的差异慢几个数量级
  • @briantist:同意。 OP 应该编写快速性能测试。在你尝试之前永远不会知道,除非你真的知道这两个函数的内部结构。
  • @wOxxOm:OP 没有询问如何改进他们所在机器的硬件。他们询问如何让代码运行得更快。
  • @DavidKnise,呃......正确的方法是加速最慢的部分,并且可以使用不同的解决方案,例如Everything API 直接读取磁盘的 MFT 并执行可能只需要几秒钟而不是几分钟的查询。
【解决方案2】:

停留在 PowerShell 和 .NET 方法领域,以下是加速函数的方法:

  • 预先计算截止时间戳一次

  • [IO.DirectoryInfo] 类型的EnumerateFiles() 方法(PSv3+ / .NET4+)与foreach 结合使用语句wOxxOm致敬。

    • EnumerateFiles() 一次枚举一个文件,保持内存使用不变,类似于Get-ChildItem,但比Get-ChildItem 快。

      • 注意事项

        • EnumerateFiles() 总是包含隐藏文件,而Get-ChildItem默认排除它们,并且仅在指定-Force时才包含它们。 p>

        • EnumerateFiles() 不适合如果由于缺少权限而有可能遇到无法访问的目录,因为即使您将 整个 foreach 语句包含在 try / @ 中987654333@ 块,您只会得到部分输出,因为迭代停止在遇到第一个不可访问的目录时。

        • 枚举顺序可以与Get-ChildItem不同。

    • PowerShell 的foreach 语句ForEach-Object cmdlet 快得多,也比 PSv4+ .ForEach() 集合快方法.

  • 直接在循环体内的每个[System.IO.FileInfo] 实例上调用.Delete() 方法。

注意:为简洁起见,下面的函数中没有错误检查,例如$numDays是否具有允许值以及$dir是否引用现有目录(如果它是基于自定义 PS 驱动器,您必须先使用Convert-Path 解决它)。

function delOld($dir, $numDays) {
    $dtCutoff = [datetime]::now - [timespan]::FromDays($numDays)
    # Make sure that the .NET framework's current dir. is the same as PS's:
    [System.IO.Directory]::SetCurrentDirectory($PWD.ProviderPath)
    # Enumerate all files recursively.
    # Replace $file.FullName with $file.Delete() to perform actual deletion.
    foreach ($file in ([IO.DirectoryInfo] $dir).EnumerateFiles('*', 'AllDirectories')) { 
     if ($file.LastWriteTime -lt $dtCutOff) { $file.FullName }
    }
}

注意:以上只是输出要删除的文件的路径;将$file.FullName 替换为$file.Delete() 以执行实际删除。

【讨论】:

  • @mklement0 我没有听说过 EnumerateFiles(),而且预先生成截止日期让我觉得自己没有早点想到它而感到愚蠢!但是,由于我正在工作的目录很大,我不愿意尝试 foreach()。不是说foreach()真的只有在数据大小小于可用内存的时候才有效吗?
  • @Deusgiggity: 不,foreach 可以安全使用,因为它一次处理一个项目(类似于 ForEach-Object cmdlet,但与 .ForEach() 集合运算符不同,后者在一个预先存在的整个集合)。由于EnumerateFiles() 也一次生成一个文件信息对象,因此这种方法即使在大目录下也适用。
【解决方案3】:

这将删除并行处理中的所有内容。

workflow delOld([string]$dir, [int]$numDays){
    $timespan = new-timespan -days $numDays
    $curTime = get-date
    $Files = get-childItem $dir -Recurse -file | where-object {(($curTime)-($_.LastWriteTime)) -gt $timespan}
    foreach -parallel ($file in $files){
        Remove-Item $File
    }

}

delOld -dir "C:\Users\AndrewD\Downloads" -numDays 8

现在如果它有很多文件夹试试这个

【讨论】:

    猜你喜欢
    • 2011-11-03
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2013-11-02
    • 2021-11-15
    • 2020-07-01
    • 2016-07-12
    • 1970-01-01
    相关资源
    最近更新 更多