Время ожидания скрипта PowerShell CSV - PullRequest
0 голосов
/ 21 февраля 2020

Я написал простой скрипт PowerShell, который считывает данные из основного CSV-файла и анализирует столбец URL-адреса.

Затем он проверяет локальную файловую систему, чтобы проверить, существует ли двоичный файл (в данном случае это файл WAV или AIFF) в подкаталоге пути сценария root, и, если это так, добавляет мета-строку данные со столбцом «Локальный файл», добавленным в новый файл CSV.

Сценарий работает отлично и выполняется примерно за 10 секунд с основным файлом CSV, содержащим выборку из примерно 2000 строк. Тем не менее, клиент в Корее говорит, что сценарий замедляется и в конечном итоге истекает с помощью основного CSV, содержащего примерно 60000 строк.

Образец анализа CSV, который я анализирую, можно найти здесь .

Мне кажется, что это проблема с памятью, вызванная $NewCSVObject, однако, так как это один из моих первых сценариев PS, это может быть просто плохое кодирование с моей стороны.

function Get-ScriptDirectory {
    Split-Path -parent $PSCommandPath
}

function Resolve-Local-CSV {
    Write-Host "Generating Local CSV File ... Please wait ..."
    $currentPath = Get-ScriptDirectory
    $csvfile = "$currentPath{0}media_local_master.csv" -f [IO.Path]::DirectorySeparatorChar
    $masterCSV = Import-Csv "$csvfile" -header _id, active, track_title, Download_URL, artist_name, album_title, composer, duration, publisher, description, url_path, beats_per_minute, file_path_uncompressed, preview_url, genres, moods, styles, instruments, keywords, last_modified -delimiter ','
    $NewCSVObject = @() 
    $masterCSV | ForEach-Object {
        $downloadUrl = $_.Download_URL
        if ($downloadUrl -ne 'Download_URL') {
            $parsedurl = $downloadUrl.split("/")
            $artist = $parsedurl[4]
            $album = $parsedurl[5]
            $track = $parsedurl[6]
            $albumTarget = "$currentPath{0}media{0}$artist{0}$album{0}" -f [IO.Path]::DirectorySeparatorChar
            $trackTarget = "$albumTarget$track"
            If ((test-path $trackTarget)) {
                $localfile = "media{0}$artist{0}$album{0}$track" -f [IO.Path]::DirectorySeparatorChar
                $_ | Add-Member -MemberType NoteProperty -Name "Local_File" -Value $localfile
                $NewCSVObject += $_ 
            }
        }
        $newLocalMaster = "$currentPath{0}media_local_master_final.csv" -f [IO.Path]::DirectorySeparatorChar
        $NewCSVObject | Export-Csv "$newLocalMaster" -notype
    }
    Write-Host "Finished Generating Local CSV File ..."
}

Resolve-Local-CSV

Ответы [ 2 ]

1 голос
/ 21 февраля 2020

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

С точки зрения эффективности кода я был в состоянии выполнять большое количество операций PowerShell в большом количестве ( 100 миллионов файлов, записей БД, объектов LDAP и т. Д. c.). Как таковой, я сделал достаточное количество базовых оценок относительной эффективности различных способов выполнения различных операций (скорость, объем памяти, возможность поиска и т. Д. c.). Стоит отметить, что я использовал эти базовые показатели на нескольких различных аппаратных платформах с различными преимуществами, и хотя результаты зависят от аппаратного обеспечения, выводы всегда одинаковы (см. Ниже). Вот небольшой пример некоторых из них:

L oop Эффективность - 10 образцов из 100 000

Код:

## Basic Loop efficiencies
1..10 | % { 
    [pscustomobject]@{
        ForeachObject = "{0:N2}" -f ((Measure-Command { 1..100000 | ForEach-Object {} }).TotalSeconds)
        Foreach       = "{0:N2}" -f ((Measure-Command { $arr = (1..100000); foreach ($i in $arr) {} }).TotalSeconds)
        For           = "{0:N2}" -f ((Measure-Command { $arr = (1..100000); for ($i=1; $i -lt $arr.Count; $i++) {}}).TotalSeconds)
        DoUntil       = "{0:N2}" -f ((Measure-Command { $i = 1; do {$i++} Until ($i -eq 99999) }).TotalSeconds)
        DoWhile       = "{0:N2}" -f ((Measure-Command { $i = 1; do {$i++} While ($i -lt 100000) }).TotalSeconds)
    }
} | Format-Table

Вывод (всего в секундах):

ForeachObject Foreach For  DoUntil DoWhile
------------- ------- ---  ------- -------
0.64          0.08    0.29 0.17    0.18   
0.66          0.07    0.33 0.16    0.17   
0.65          0.08    0.29 0.17    0.17   
0.72          0.07    0.31 0.19    0.16   
0.64          0.07    0.28 0.16    0.16   
0.62          0.07    0.30 0.19    0.19   
0.73          0.07    0.31 0.16    0.18   
0.73          0.08    0.33 0.17    0.17   
0.64          0.08    0.32 0.16    0.18   
0.65          0.07    0.29 0.16    0.16

Создание массива - 10 образцов добавления 10K, 25K, 50K

Код:

## Basic Array Appending
1..10 | % { 
    [pscustomobject]@{
        Array_PlusEquals_10K     = "{0:N2}" -f ((Measure-Command { $out = @(); $arr = (1..10000); foreach ($i in $arr) { $out += $i }}).TotalSeconds)
        ArrayList_Add_Method_10K = "{0:N2}" -f ((Measure-Command { $out = [System.Collections.ArrayList]::new(); $arr = (1..10000); foreach ($i in $arr) { [void]$out.Add($i) }}).TotalSeconds)
        Array_Assignment_10K     = "{0:N2}" -f ((Measure-Command { $arr = (1..10000); $out = foreach ($i in $arr) { $i }}).TotalSeconds)
        Array_PlusEquals_25K     = "{0:N2}" -f ((Measure-Command { $out = @(); $arr = (1..25000); foreach ($i in $arr) { $out += $i }}).TotalSeconds)
        ArrayList_Add_Method_25K = "{0:N2}" -f ((Measure-Command { $out = [System.Collections.ArrayList]::new(); $arr = (1..25000); foreach ($i in $arr) { [void]$out.Add($i) }}).TotalSeconds)
        Array_Assignment_25K     = "{0:N2}" -f ((Measure-Command { $arr = (1..25000); $out = foreach ($i in $arr) { $i }}).TotalSeconds)
        Array_PlusEquals_50K     = "{0:N2}" -f ((Measure-Command { $out = @(); $arr = (1..50000); foreach ($i in $arr) { $out += $i }}).TotalSeconds)
        ArrayList_Add_Method_50K = "{0:N2}" -f ((Measure-Command { $out = [System.Collections.ArrayList]::new(); $arr = (1..50000); foreach ($i in $arr) { [void]$out.Add($i) }}).TotalSeconds)
        Array_Assignment_50K     = "{0:N2}" -f ((Measure-Command { $arr = (1..50000); $out = foreach ($i in $arr) { $i }}).TotalSeconds)
    }
} | Format-Table

Вывод (всего в секундах):

Array_PlusEquals_10K ArrayList_Add_Method_10K Array_Assignment_10K Array_PlusEquals_25K ArrayList_Add_Method_25K Array_Assignment_25K Array_PlusEquals_50K ArrayList_Add_Method_50K Array_Assignment_50K
-------------------- ------------------------ -------------------- -------------------- ------------------------ -------------------- -------------------- ------------------------ --------------------
3.32                 0.04                     0.01                 20.64                0.05                     0.04                 79.97                0.10                     0.07                
3.15                 0.02                     0.01                 20.68                0.05                     0.03                 80.90                0.11                     0.07                
3.23                 0.05                     0.01                 21.30                0.04                     0.04                 82.65                0.09                     0.07                
3.16                 0.02                     0.01                 21.46                0.05                     0.03                 80.05                0.09                     0.07                
3.16                 0.02                     0.01                 21.33                0.04                     0.04                 87.93                0.10                     0.07                
3.04                 0.02                     0.01                 19.65                0.05                     0.03                 80.12                0.09                     0.07                
3.05                 0.02                     0.01                 19.81                0.05                     0.03                 81.22                0.11                     0.07                
3.04                 0.02                     0.01                 19.60                0.05                     0.04                 79.62                0.12                     0.07                
3.16                 0.02                     0.01                 20.69                0.05                     0.04                 82.85                0.10                     0.07                
3.31                 0.02                     0.01                 21.95                0.05                     0.03                 81.19                0.11                     0.07                

Выводы:

Петли:

  • foreach в подавляющем большинстве лучших l oop где его можно использовать
  • ForeachObject является в подавляющем большинстве худшим конструктом l oop

Добавление данных в массивы:

  • Назначение лучше, но ArrayList.Add () - вторая секунда - даже в масштабе
  • Array + = следует избегать любой ценой. На самом деле становится все хуже по сравнению с большими наборами данных
1 голос
/ 21 февраля 2020

Если это проблема с памятью, то синтаксис $NewCSVObject += $_ не оказывает вам никакой пользы. Я хотел бы сделать по крайней мере следующие изменения.

# Change the following
$NewCSVObject += $_ # Replace This Line
$_ # Replacement Code

$masterCSV | ForEach-Object { # Replace this Line
$NewCSVObject = $masterCSV | ForEach-Object { # Replacement Code

$NewCSVObject | Export-Csv "$newLocalMaster" -notype # Move this line to outside of the Foreach-Object {} script block.

Объяснение:

Использование += для эффективного добавления элементов в массив создает новую копию текущей массив со всеми его текущими элементами, а затем добавляет дополнительные элементы в вывод. По мере роста массива эта операция становится все более дорогостоящей.

При использовании скриптового блока foreach l oop или Foreach-Object {} любой вывод, созданный в этих конструкциях, может быть сохранен в переменной, просто назначив переменная для команды l oop или. Переменная будет коллекцией, если имеется более одного вывода. Ниже приведен пример синтаксиса, иллюстрирующий концепцию.

$variable = foreach ($item in $Collection) { 
    $item # $item is output but stored in $variable instead of being sent to stdout/console
}

$variable = $collection | Foreach-Object { 
    $_ # The current pipeline object is output but stored in $variable instead of being sent to stdout/console
}

Вы должны выводить данные в файл CSV только в конце действий l oop, если другие системные ограничения не мешают вам сделать это. Если вы выводите в CSV постоянно, вам нужно использовать переключатель -Append на Export-Csv для предотвращения перезаписи предыдущих данных.

...