Повышение производительности импорта 2 CSV-файлов, сравнения, группировки и вывода результатов в журнал - PullRequest
0 голосов
/ 27 октября 2018

Я довольно новичок в powershell в целом, поэтому я хотел бы получить несколько советов о том, как улучшить производительность моего кода. Цель этого сценария - импортировать 2 CSV-файла, сравнить их и вывести результаты в файл журнала (или два, в зависимости от выбранной опции).

Мой оригинальный скрипт работает нормально, но не очень быстро. В моей тестовой среде импорт, сравнение, группирование и вывод журналов занимает около 15 секунд для всех ~ 440 файлов, содержащихся в двух файлах CSV. 15 с - это немного, но я запусту этот скрипт с 600 000+ файлами, поэтому чем быстрее, тем лучше.

#User variables
$logpath = "X:\Documents\Customer Projects"
$passlog = $true

$startt = (Get-Date)

# code to check to see if necessary files exist and/or are locked is here

$csv1 = Import-CSV $generated
$csv2 = Import-CSV $cloud
$comp = Compare-Object -ReferenceObject $csv2 -DifferenceObject $csv1 -Property Name,Size,Hash -PassThru
$group = $comp | Group-Object -Property Name
$failcount = ($group | Measure-Object).count
If ($passlog) {
    $comp = Compare-Object -ReferenceObject $csv2 -DifferenceObject $csv1 -IncludeEqual -Property Name,Size,Hash
    $group = $comp | Group-Object -Property Name
    $count = ($group | Measure-Object).count
}
Else {
    $count = $failcount
}
$curr = 0
Write-Output "($failcount) files failed verification. See logs and below for details:"
Write-Output "`n"
foreach ($file in $group)
{
    $source = $comp | Where-Object {($_.SideIndicator -eq "<=" -and $_.Name -eq $file.name)}
    $dest = $comp | Where-Object {($_.SideIndicator -eq "=>" -and $_.Name -eq $file.name)}
    $pass = $comp | Where-Object {($_.SideIndicator -eq "==" -and $_.Name -eq $file.name)}
    $name = $file.name
    $sourcesize = $source | Select -ExpandProperty Size
    $sourcehash = $source | Select -ExpandProperty Hash
    $destsize = $dest | Select -ExpandProperty Size
    $desthash = $dest | Select -ExpandProperty Hash
    $destpath = ($csv1 | Where-Object {$_.Name -eq $file.name} | Select -ExpandProperty DestPath)
    $countp = ($file | Select -ExpandProperty Count)
    $curr += 1
    $logger = $true
    if ($countp -ge 2 -and ($source) -and ($dest)) {
        Write-Host "($curr of $count) Hash mis-match            -"$file.name""
        $message = "Hash mis-match"
    }
    if ($countp -eq 1 -and ($pass)) {
        Write-Host "($curr of $count) Verification passed!      -"$file.name""
        $message = "Verification passed"
        $sourcesize = ($pass | Select -ExpandProperty Size)
        $sourcehash = ($pass | Select -ExpandProperty Hash)
        $destsize = $sourcesize
        $desthash = $sourcehash
        $logger = $false
    }
    if ($countp -eq 1 -and ($source)) {
        Write-Host "($curr of $count) Missing from destination  -"$file.name""
        $message = "File missing from destination"
    }
    if ($countp -eq 1 -and ($dest)) {
        Write-Host "($curr of $count) Missing from source       -"$file.name""
        $message = "File missing from source"
    }
    If ($logger) {
        $whichlog = $failed   
    }
    Else {
        $whichlog = $passed
    }
    "" | Select @{Name="Status";Expression={$message}},@{N="FileName";E={$name}},@{N="SourceSize";E={$sourcesize}},@{N="SourceHash";E={$sourcehash}},@{N="DestSize";E={$destsize}},@{N="DestinationHash";E={$desthash}},@{N="DestinationPath";E={$destpath}} | Export-Csv -Path $whichlog -Append -NoTypeInformation
}
$endt = (Get-Date)
$runt = ($endt - $startt)
$exectime = "$($runt.hours)h:$($runt.minutes)m:$($runt.seconds)s"
Write-Output "`n"
Write-Output "Completed in $exectime! Press any key to close...";

Я читал об улучшении производительности с помощью хеш-таблиц и не добавляя файлы или массивы. Я не понял, как правильно использовать хеш-таблицы, но рефакторинг для записи в CSV сразу в конце сценария вместо добавления каждого цикла в цикл обеспечивает хороший прирост производительности. Использование приведенных ниже изменений сокращает время до 10 секунд, что является хорошим улучшением, но я чувствую, что 10 секунд все еще слишком длинны.

# same code before this point
Write-Host "Starting verification process. This may take some time..."
$array = foreach ($file in $group)
{
    $source = $comp | Where-Object {($_.SideIndicator -eq "<=" -and $_.Name -eq $file.name)}
    $dest = $comp | Where-Object {($_.SideIndicator -eq "=>" -and $_.Name -eq $file.name)}
    $pass = $comp | Where-Object {($_.SideIndicator -eq "==" -and $_.Name -eq $file.name)}
    $name = $file.name
    $sourcesize = $source.size
    $sourcehash = $source.hash
    $destsize = $dest.size
    $desthash = $dest.hash
    $destpath = ($csv1 | Where-Object {$_.Name -eq $file.name} | Select -ExpandProperty DestPath)
    $countp = ($file | Select -ExpandProperty Count)
    $curr += 1
    $logger = $true
    if ($countp -eq 1 -and ($pass)) {
        Write-Host "($curr of $count) Verification passed!      -"$file.name""
        $message = "Verification passed"
        $sourcesize = ($pass.size)
        $sourcehash = ($pass.hash)
        $destsize = $sourcesize
        $desthash = $sourcehash
    }
    if ($countp -ge 2 -and ($source) -and ($dest)) {
        #Write-Host "($curr of $count) Hash mis-match            -"$file.name""
        $message = "Hash mis-match"
    }
    if ($countp -eq 1 -and ($source)) {
        #Write-Host "($curr of $count) Missing from destination  -"$file.name""
        $message = "File missing from destination"
    }
    if ($countp -eq 1 -and ($dest)) {
        #Write-Host "($curr of $count) Missing from source       -"$file.name""
        $message = "File missing from source"
    }
    $file | Select @{N="Status";E={$message}},@{N="FileName";E={$name}},@{N="SourceSize";E={$sourcesize}},@{N="SourceHash";E={$sourcehash}},@{N="DestinationSize";E={$destsize}},@{N="DestinationHash";E={$desthash}},@{N="DestinationPath";E={$destpath}}
}
$array | Where-Object {$_.Status -ne "Verification passed"} | Export-Csv -Path $failed -NoTypeInformation
If ($passlog) {
    $array | Where-Object {$_.Status -eq "Verification passed"} | Export-Csv -Path $passed -NoTypeInformation
}
# same code after this point

Я считаю, что следующим логическим шагом было бы каким-то образом собрать всю необходимую информацию в одном месте, чтобы цикл ForEach мог быстро искать необходимые данные, а не сортировать, фильтровать или иным образом выполнять ненужные вычисления. Я попытался сделать это, как показано в приведенном ниже коде, а затем запустил второй цикл, чтобы захватить поля и сгенерировать сообщение о состоянии, но производительность была ниже, чем у моего оригинала (вероятно, из-за большей фильтрации для первого ForEach петля по сравнению с оригиналом). Есть предложения?

$array = ForEach ($item in $group) {
    $name = $item.name
    $count = $item.Count
    $SideInd = $comp | Where-Object {($_.Name -eq ($item.name))} | Select -ExpandProperty SideIndicator
    $SourceS = $csv2 | Where-Object {($_.Name -eq ($item.name))} | Select -ExpandProperty Size
    $SourceH = $csv2 | Where-Object {($_.Name -eq ($item.Name))} | Select -ExpandProperty Hash
    $DestS = $csv1 | Where-Object {($_.Name -eq $item.Name)} | Select -ExpandProperty Size
    $DestH = $csv1 | Where-Object {($_.Name -eq $item.Name)} | Select -ExpandProperty Hash
    $DestP = $csv1 | Where-Object {($_.Name -eq $item.Name)} | Select -ExpandProperty DestPath
    $Item | Select name, count, @{N="SideInd";E={$SideInd}}, @{N="SourceSize";E={$SourceS}}, @{N="SourceHash";E={$SourceH}}, @{N="DestinationSize";E={$DestS}}, @{N="DestinationHash";E={$DestH}}, @{N="DestinationPath";E={$DestP}}
}
...