Я довольно новичок в 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}}
}