【问题标题】:Performance of PowerShell script reading files is too slowPowerShell脚本读取文件的性能太慢
【发布时间】:2016-07-14 04:19:52
【问题描述】:

我目前正在编写一个 PowerShell 脚本,该脚本将作为构建步骤的一部分在 TeamCity 中使用。脚本必须:

  • 递归检查文件夹中具有特定扩展名(.item)的所有文件,
  • 读取每个文件的第三行(其中包含一个 GUID)并检查这些行中是否有任何重复项,
  • 记录包含重复 GUID 的文件的路径并记录 GUID 本身,
  • 如果发现一个或多个重复项,则使 TeamCity 构建失败

我对 PowerShell 脚本完全陌生,但到目前为止,我已经做了一些我期望它做的事情:

Write-Host "Start checking for Unicorn serialization errors."

$files = get-childitem "%system.teamcity.build.workingDir%\Sitecore\serialization" -recurse -include *.item | where {! $_.PSIsContainer} | % { $_.FullName }
$arrayOfItemIds = @()
$NrOfFiles = $files.Length
[bool] $FoundDuplicates = 0

Write-Host "There are $NrOfFiles Unicorn item files to check."

foreach ($file in $files)
{
    $thirdLineOfFile = (Get-Content $file)[2 .. 2]

    if ($arrayOfItemIds -contains $thirdLineOfFile)
    {
        $FoundDuplicates = 1
        $itemId = $thirdLineOfFile.Split(":")[1].Trim()

        Write-Host "Duplicate item ID found!"
        Write-Host "Item file path: $file"
        Write-Host "Detected duplicate ID: $itemId"
        Write-Host "-------------"
        Write-Host ""
    }
    else
    {
        $arrayOfItemIds += $thirdLineOfFile
    }
}

if ($foundDuplicates)
{
    "##teamcity[buildStatus status='FAILURE' text='One or more duplicate ID's were detected in Sitecore serialised items. Check the build log to see which files and ID's are involved.']"
    exit 1
}

Write-Host "End script checking for Unicorn serialization errors."

问题是:它很慢!该脚本必须检查的文件夹当前包含超过 14.000 个 .item-files,并且该数量很可能在未来只会继续增加。我知道打开和读取这么多文件是一项繁重的操作,但没想到大约需要半个小时才能完成。这太长了,因为这意味着每个(快照)构建的构建时间都会延长半小时,这是不可接受的。我曾希望脚本能在几分钟内完成。

我简直不敢相信没有更快的方法可以做到这一点。因此,非常感谢您在这方面的任何帮助!

解决方案

好吧,我不得不说,到目前为止,我收到的所有 3 个答案都对我有所帮助。我首先直接使用 .NET 框架类,然后也使用字典来解决不断增长的数组问题。运行我自己的脚本大约需要 30 分钟,然后使用 .NET 框架类缩短到 2 分钟。使用 Dictionary 解决方案后,它只需要 6 或 7 秒!我使用的最终脚本:

Write-Host "Start checking for Unicorn serialization errors."

[String[]] $allFilePaths = [System.IO.Directory]::GetFiles("%system.teamcity.build.workingDir%\Sitecore\serialization", "*.item", "AllDirectories")
$IdsProcessed = New-Object 'system.collections.generic.dictionary[string,string]'
[bool] $FoundDuplicates = 0
$NrOfFiles = $allFilePaths.Length

Write-Host "There are $NrOfFiles Unicorn item files to check."
Write-Host ""

foreach ($filePath in $allFilePaths)
{
    [System.IO.StreamReader] $sr = [System.IO.File]::OpenText($filePath)
    $unused1 = $sr.ReadLine() #read the first unused line
    $unused2 = $sr.ReadLine() #read the second unused line
    [string]$thirdLineOfFile = $sr.ReadLine()
    $sr.Close()

    if ($IdsProcessed.ContainsKey($thirdLineOfFile))
    {
        $FoundDuplicates = 1
        $itemId = $thirdLineOfFile.Split(":")[1].Trim()
        $otherFileWithSameId = $IdsProcessed[$thirdLineOfFile]

        Write-Host "---------------"
        Write-Host "Duplicate item ID found!"
        Write-Host "Detected duplicate ID: $itemId"
        Write-Host "Item file path 1: $filePath"
        Write-Host "Item file path 2: $otherFileWithSameId"
        Write-Host "---------------"
        Write-Host ""
    }
    else
    {
        $IdsProcessed.Add($thirdLineOfFile, $filePath)
    }
}

if ($foundDuplicates)
{
    "##teamcity[buildStatus status='FAILURE' text='One or more duplicate ID|'s were detected in Sitecore serialised items. Check the build log to see which files and ID|'s are involved.']"
    exit 1
}

Write-Host "End script checking for Unicorn serialization errors. No duplicate ID's were found."

谢谢大家!

【问题讨论】:

  • 你可能是文件 IO 带宽受限。因此,大部分时间(可能)用于将文件拖出磁盘。如果是这种情况(并且信封计算的背面应该能够确认它),那么加速它的最简单方法是转移到更快的存储 - 例如 SSD。
  • @Mike Wise:本来可以,尽管在任何现代机器上,即使复制 14000 个文件半小时也太长了。我认为在这种情况下,文件 IO 根本不是问题。由于他的代码是“构建步骤的一部分”,所有文件都将在他的代码启动之前被处理或创建,因此很可能所有文件都仍在磁盘缓存中。

标签: performance powershell teamcity


【解决方案1】:

尝试将Get-Content 替换为[System.IO.File]::ReadLines。如果这仍然太慢,请考虑使用System.IO.StreamReader - 这会导致您编写更多代码但允许您只阅读前 3 行。

【讨论】:

    【解决方案2】:

    不清楚当您使用 Get-ChildItem 和 Get-Content 等高级命令时 PowerShell 究竟做了什么。所以我会更明确一点,直接使用 .NET 框架类。

    使用获取文件夹中文件的路径

    [String[]] $files = [System.IO.Directory]::GetFiles($folderPath, "*.yourext")
    

    然后,不要使用 Get-Content,而是打开每个文件并读取前三行。像这样:

    [System.IO.StreamReader] $sr = [System.IO.File]::OpenText(path)
    [String]$line = $sr.ReadLine()
    while ($line -ne $null)
    {
      # do your thing, break when you know enough
      # ...
      [String]$line = $sr.ReadLine()
    }
    $sr.Close()
    

    我可能犯了一两个错误,我懒得起来在电脑上测试。

    您可能需要考虑重新设计您的构建系统以使用更少的文件。 14000 个文件和增长似乎没有必要。如果可以将一些数据合并到更少的文件中,也可能对性能有很大帮助。

    要检查重复的 guid,请使用 Dictionary 类,其中字符串是您的文件名。然后,如果发现任何重复项,您可以报告其所在位置。

    【讨论】:

    • 我们在 Sitecore 项目中使用此脚本。当您为 Sitecore 开发时,您主要构建 C# 代码,但“开发”的某些部分必须在 CMS 中完成(即:在数据库中)。因此,我们的数据库由一个名为 Unicorn 的工具自动序列化。它将 CMS 中的每个“项目”存储到磁盘上的序列化文件中。这样,所有数据库开发也都存储在 Git 中,并且易于分发给其他开发人员。它工作得很好,但这样做的缺点是文件数量巨大......但没有其他方法可以做到这一点,因此重新设计不是一种选择。
    【解决方案3】:

    我认为您的问题可能是由您的阵列引起的,并且可能不是文件读取问题。

    1. PowerShell 中数组的大小是不可变的,因此每次向数组中添加项时,它都会创建一个新数组并复制所有项。

    2. 您的数组通常不会包含正在查找的值,并且必须将 $thirdLineOfFile 与不断增长的数组中的每个项目进行比较。

    我一直在使用 .Net 词典来解决这个问题。 (或者当我不进行大量查找时使用 ArrayLists)MSDN Dictionary Reference

    注意:PowerShell 提供了一个名为“Measure-Command”的 Cmdlet,您可以使用它来确定脚本的哪个部分实际上运行缓慢。我会测试文件读取时间和时间来增加数组和查找值。根据文件的大小,您实际上也可能在那里遇到性能问题。

    这是适用于使用 .Net 字典的代码。我重命名了你的变量,因为它不再是一个数组了。

    Write-Host "Start checking for Unicorn serialization errors."
    
    $files = get-childitem "%system.teamcity.build.workingDir%\Sitecore\serialization" -recurse -include *.item | where {! $_.PSIsContainer} | % { $_.FullName }
    #$arrayOfItemIds = @()
    $IdsProcessed = New-Object 'system.collections.generic.dictionary[string,string]' # A .Net Dictionary will be faster for inserts and lookups.
    $NrOfFiles = $files.Length
    [bool] $FoundDuplicates = 0
    
    Write-Host "There are $NrOfFiles Unicorn item files to check."
    
    foreach ($file in $files)
    {
        $thirdLineOfFile = (Get-Content -path $file -TotalCount 3)[2] # TotalCount param will let us pull in just the beginning of the file.
    
        #if ($arrayOfItemIds -contains $thirdLineOfFile)
        if($IdsProcessed.ContainsKey($thirdLineOfFile))
        {
            $FoundDuplicates = 1
            $itemId = $thirdLineOfFile.Split(":")[1].Trim()
    
            Write-Host "Duplicate item ID found!"
            Write-Host "Item file path: $file"
            Write-Host "Detected duplicate ID: $itemId"
            Write-Host "-------------"
            Write-Host ""
        }
        else
        {
            #$arrayOfItemIds += $thirdLineOfFile
            $IdsProcessed.Add($thirdLineOfFile,$null) 
        }
    }
    
    if ($foundDuplicates)
    {
        "##teamcity[buildStatus status='FAILURE' text='One or more duplicate ID's were detected in Sitecore serialised items. Check the build log to see which files and ID's are involved.']"
        exit 1
    }
    
    Write-Host "End script checking for Unicorn serialization errors."
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2023-04-05
      • 1970-01-01
      • 1970-01-01
      • 2016-10-06
      • 2018-02-12
      • 2017-08-23
      相关资源
      最近更新 更多