Как ускорить Powershell Get-Childitem по сравнению с UNC - PullRequest
16 голосов
/ 26 августа 2011

DIR или GCI медленно в Powershell, но быстро в CMD.Есть ли способ ускорить это?

В CMD.exe, после задержки менее секунды, это отвечает так же быстро, как окно CMD может поддерживать

dir \\remote-server.domain.com\share\folder\file*.*

В Powershell (v2), после 40+ секундной задержки, это реагирует с заметной медлительностью (возможно, 3-4 строки в секунду)

gci \\remote-server.domain.com\share\folder\file*.*

Я пытаюсь сканировать журналы на удаленном сервере, так что, возможно, естьболее быстрый подход.

get-childitem \\$s\logs -include $filemask -recurse | select-string -pattern $regex

Ответы [ 4 ]

16 голосов
/ 26 августа 2011

Хорошо, вот как я это делаю, и, похоже, работает.

$files = cmd /c "$GETFILESBAT \\$server\logs\$filemask"
foreach( $f in $files ) {
    if( $f.length -gt 0 ) {
        select-string -Path $f -pattern $regex | foreach-object { $_ }
    }
}

Тогда $ GETFILESBAT указывает на это:

@dir /a-d /b /s %1
@exit

Я пишу и удаляю этот BAT-файл из скрипта PowerShell, поэтому я полагаю, что это решение только для PowerShell, но оно не использует только PowerShell.

Мои предварительные показатели эффективности показывают, что это в одиннадцать тысяч раз быстрее.

Я тестировал gci против cmd dir и FileIO.FileSystem.GetFiles из ссылки @Shawn Melton link .

Суть в том, что для ежедневного использования на локальных дисках GetFiles - самый быстрый. На сегодняшний день . CMD DIR респектабельно. Как только вы вводите более медленное сетевое соединение со многими файлами, CMD DIR немного быстрее, чем GetFiles. Тогда Get-ChildItem ... вау, это варьируется от не очень плохого до ужасного, в зависимости от количества задействованных файлов и скорости соединения.

Некоторые тестовые прогоны. Я переместил GCI в тестах, чтобы убедиться, что результаты были последовательными.

10 итераций сканирования c:\windows\temp для * .tmp файлов

.\test.ps1 "c:\windows\temp" "*.tmp" 10
GCI... 00:00:01.1391139
GetFiles... 00:00:00.0570057
CMD dir... 00:00:00.5360536

GetFiles в 10 раз быстрее CMD dir, что само по себе более чем в 2 раза быстрее, чем GCI.

10 итераций сканирования c:\windows\temp для * .tmp файлов с рекурсией

.\test.ps1 "c:\windows\temp" "*.tmp" 10 -recurse
GetFiles... 00:00:00.7020180
CMD dir... 00:00:00.7644196
GCI... 00:00:04.7737224

GetFiles немного быстрее, чем CMD dir, и оба почти в 7 раз быстрее, чем GCI.

10 итераций сканирования локального сервера в другом домене на наличие файлов журнала приложений

.\test.ps1 "\\closeserver\logs\subdir" "appname*.*" 10
GCI... 00:00:06.0796079
GetFiles... 00:00:00.3590359
CMD dir... 00:00:00.6270627

GetFiles примерно в 2 раза быстрее, чем CMD dir, сам в 10 раз быстрее, чем GCI.

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

.\test.ps1 "\\distantserver.company.com\logs\subdir" "appname.2011082*.*"
GCI... 00:11:09.5525579
GetFiles... 00:00:00.4360436
CMD dir... 00:00:00.3340334

CMD dir быстрее всего отправляется на удаленный сервер со многими файлами, но GetFiles находится довольно близко. С другой стороны, GCI медленнее в пару тысяч раз.

Две итерации сканирования удаленного сервера в другом домене на наличие файлов журнала приложения со многими файлами

.\test.ps1 "\\distantserver.company.com\logs\subdir" "appname.20110822*.*" 2
GetFiles... 00:00:01.4976384
CMD dir... 00:00:00.9360240
GCI... 00:22:17.3068616

Более или менее линейное увеличение при увеличении итераций теста.

Одна итерация сканирования удаленного сервера в другом домене на наличие файлов журнала приложений с меньшим количеством файлов

.\test.ps1 "\\distantserver.company.com\logs\othersubdir" "appname.2011082*.*" 10
GCI... 00:00:01.9656630
GetFiles... 00:00:00.5304170
CMD dir... 00:00:00.6240200

Здесь GCI не так уж и плох, GetFiles в 3 раза быстрее, а CMD dir отстает.

Заключение

GCI нужна опция -raw или -fast, которая не пытается делать так много. Между тем, GetFiles - это здоровая альтернатива, которая лишь иногда немного медленнее, чем CMD dir, и обычно быстрее (из-за появления CMD.exe?).

Для справки вот код test.ps1.

param ( [string]$path, [string]$filemask, [switch]$recurse=$false, [int]$n=1 )
[reflection.assembly]::loadwithpartialname("Microsoft.VisualBasic") | Out-Null
write-host "GetFiles... " -nonewline
$dt = get-date;
for($i=0;$i -lt $n;$i++){
  if( $recurse ){ [Microsoft.VisualBasic.FileIO.FileSystem]::GetFiles( $path,
      [Microsoft.VisualBasic.FileIO.SearchOption]::SearchAllSubDirectories,$filemask
    )  | out-file ".\testfiles1.txt"}
  else{ [Microsoft.VisualBasic.FileIO.FileSystem]::GetFiles( $path,
      [Microsoft.VisualBasic.FileIO.SearchOption]::SearchTopLevelOnly,$filemask
    )  | out-file ".\testfiles1.txt" }}
$dt2=get-date;
write-host $dt2.subtract($dt)
write-host "CMD dir... " -nonewline
$dt = get-date;
for($i=0;$i -lt $n;$i++){
  if($recurse){
    cmd /c "dir /a-d /b /s $path\$filemask" | out-file ".\testfiles2.txt"}
  else{ cmd /c "dir /a-d /b $path\$filemask" | out-file ".\testfiles2.txt"}}
$dt2=get-date;
write-host $dt2.subtract($dt)
write-host "GCI... " -nonewline
$dt = get-date;
for($i=0;$i -lt $n;$i++){
  if( $recurse ) {
    get-childitem "$path\*" -include $filemask -recurse | out-file ".\testfiles0.txt"}
  else {get-childitem "$path\*" -include $filemask | out-file ".\testfiles0.txt"}}
$dt2=get-date;
write-host $dt2.subtract($dt)
15 голосов
/ 26 августа 2011

Здесь - хорошее объяснение того, почему Get-ChildItem работает медленно, по словам Холмса.Если принять к сведению комментарий от «Anon 11 Mar 2010 11:11 AM» внизу страницы, его решение может работать на вас.

Код Anon:

# SCOPE: SEARCH A DIRECTORY FOR FILES (W/WILDCARDS IF NECESSARY)
# Usage:
# $directory = "\\SERVER\SHARE"
# $searchterms = "filname[*].ext"
# PS> $Results = Search $directory $searchterms

[reflection.assembly]::loadwithpartialname("Microsoft.VisualBasic") | Out-Null

Function Search {
  # Parameters $Path and $SearchString
  param ([Parameter(Mandatory=$true, ValueFromPipeline = $true)][string]$Path,
  [Parameter(Mandatory=$true)][string]$SearchString
  )
  try {
    #.NET FindInFiles Method to Look for file
    # BENEFITS : Possibly running as background job (haven't looked into it yet)

    [Microsoft.VisualBasic.FileIO.FileSystem]::GetFiles(
    $Path,
    [Microsoft.VisualBasic.FileIO.SearchOption]::SearchAllSubDirectories,
    $SearchString
    )
  } catch { $_ }

}
2 голосов
/ 20 августа 2014

Я попробовал некоторые из предложенных методов с большим количеством файлов (~ 190.000). Как упомянуто в комментарии Кайла, GetFiles здесь не очень полезен, потому что это нужно почти навсегда.

cmd dir был лучше, чем Get-ChildItems в моих первых тестах, но, похоже, GCI сильно ускоряется, если вы используете параметр -Force. При этом необходимое время было примерно таким же, как для cmd dir.

P.S .: В моем случае мне пришлось исключить большинство файлов из-за их расширения. Это было сделано с -Exclude в gci и | где в других командах. Таким образом, результаты поиска файлов могут немного отличаться.

0 голосов
/ 14 мая 2019

Вот интерактивный ридер, который анализирует cmd /c dir (который может обрабатывать unc пути) и соберет 3 наиболее важных свойства для большинства людей: полный путь, размер, временная метка

использование будет что-то вроде $files_with_details = $faster_get_files.GetFileList($unc_compatible_folder)

и есть вспомогательная функция для проверки комбинированного размера $faster_get_files.GetSize($files_with_details)

$faster_get_files = New-Module -AsCustomObject -ScriptBlock {
    #$DebugPreference = 'Continue' #verbose, this will take figuratively forever
    #$DebugPreference = 'SilentlyContinue'
    $directory_filter = "Directory of (.+)"
    $file_filter = "(\d+/\d+/\d+)\s+(\d+:\d+ \w{2})\s+([\d,]+)\s+(.+)" # [1] is day, [2] is time (AM/PM), [3] is size,  [4] is filename
    $extension_filter = "(.+)[\.](\w{3,4})" # [1] is leaf, [2] is extension
    $directory = ""
    function GetFileList ($directory = $this.directory)
    {
        if ([System.IO.Directory]::Exists($directory))
        {
            # Gather raw file list
            write-Information "Gathering files..."
            $files_raw = cmd /c dir $folder\*.* /s/a-d

            # Parse file list
            Write-Information "Parsing file list..."
            $directory = $folder
            $files_with_details = foreach ($line in $files_raw)
            {
                Write-Debug "starting line {$($line)}"
                Switch -regex ($line)
                {
                    $this.directory_filter
                    {
                        $directory = $matches[1]
                        break
                    }
                    $this.file_filter
                    {
                        Write-Debug "parsing matches {$($matches.value -join ";")}"
                        $date     = $matches[1]
                        $time     = $matches[2] # am/pm style
                        $size     = $matches[3]
                        $filename = $matches[4]

                        # we do a second match here so as to not append a fake period to files without an extension, otherwise we could do a single match up above
                        Write-Debug "parsing extension from {$($filename)}"
                        if ($filename -match $this.extension_filter) 
                        {
                            $file_leaf = $matches[1]
                            $file_extension = $matches[2]
                        }
                        else
                        {
                            $file_leaf = $filename
                            $file_extension = ""
                        }
                        [pscustomobject][ordered]@{
                            "fullname"  = [string]"$($directory)\$($filename)"
                            "filename"  = [string]$filename
                            "folder"    = [string]$directory
                            "file_leaf" = [string]$file_leaf
                            "extension" = [string]$file_extension
                            "date"      = get-date "$($date) $($time)"
                            "size"      = [int]$size
                        }
                        break
                    } 
                } # finish directory/file test
            } # finish all files
            return $files_with_details
        } #finish directory exists test
        else #directory doesn't exist
        {throw("Directory not found")}
    }
    function GetSize($files_with_details)
    {
        $combined_size = ($files_with_details|measure -Property size -sum).sum
        $pretty_size_gb = "$([math]::Round($combined_size / 1GB, 4)) GB"
        return $pretty_size_gb
    }
    Export-ModuleMember -Function * -Variable *
}
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...