web-dev-qa-db-ja.com

UNCを介してPowershellGet-Childitemを高速化する方法

DIRまたはGCIは、Powershellでは低速ですが、CMDでは高速です。これをスピードアップする方法はありますか?

CMD.exeでは、1秒未満の遅延の後、これはCMDウィンドウが追いつくことができるのと同じ速さで応答します

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

Powershell(v2)では、40秒以上の遅延の後、これは顕著な速度低下で応答します(おそらく、1秒あたり3〜4行)

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

リモートサーバーでログをスキャンしようとしているので、もっと速い方法があるかもしれません。

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

Here は、LeeHolmesによるGet-ChildItemが遅い理由の良い説明です。ページの下部にある「Anon11Mar 2010 11:11 AM」からのコメントに注意すると、彼の解決策が役立つ可能性があります。

アノンのコード:

# 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 { $_ }

}
15
Shawn Melton

さて、これは私がそれをしている方法です、そしてそれはうまくいくようです。

$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だけを使用しているわけではありません。

私の予備的なパフォーマンスメトリクスは、これが11,000倍速いことを示しています。

@Shawn Meltonが参照している link からgci vs. cmd dir vs.FileIO.FileSystem.GetFilesをテストしました。

要するに、ローカルドライブで毎日使用する場合は、GetFilesが最速です。 はるかにCMD DIRは立派です。多くのファイルで低速のネットワーク接続を導入すると、CMD DIRGetFilesよりもわずかに高速になります。次に、Get-ChildItem...うわー、これは、関係するファイルの数と接続の速度に応じて、それほど悪くないものからひどいものまでさまざまです。

いくつかのテストが実行されます。結果が一貫していることを確認するために、テストでGCIを移動しました。

*。tmpファイルのスキャンc:\windows\tempを10回繰り返します

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

GetFilesはCMDdirよりも10倍高速であり、CMDdir自体はGCIよりも2倍以上高速です。

再帰を伴う* .tmpファイルのスキャンc:\windows\tempの10回の反復

.\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はCMDdirよりも少し高速で、どちらもGCIよりもほぼ7倍高速です。

別のドメインのオンサイトサーバーでアプリケーションログファイルをスキャンする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はCMDdirよりも約2倍高速で、GCIよりも10倍高速です。

多くのファイルが関係している、別のドメインの離れたサーバーをスキャンしてアプリケーションログファイルを1回繰り返す

.\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は数千倍遅いです。

多くのファイルがある別のドメインの離れたサーバーをスキャンしてアプリケーションログファイルを2回繰り返す

.\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

テストの反復が増えるにつれて、多かれ少なかれ線形に増加します。

ファイルの数が少ない、別のドメインの離れたサーバーをスキャンしてアプリケーションログファイルを1回繰り返す

.\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ディレクトリはすぐ後ろにあります。

結論

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)
16
b w

大量のファイル(〜190.000)を使用して、提案された方法のいくつかを試しました。カイルのコメントで述べたように、GetFilesはほぼ永久に必要なので、ここではあまり役に立ちません。

私の最初のテストでは、cmddirはGet-ChildItemsよりも優れていましたが、-Forceパラメーターを使用すると、GCIの速度が大幅に向上するようです。これにより、必要な時間はcmddirの場合とほぼ同じになりました。

追伸:私の場合、拡張子が原因でほとんどのファイルを除外する必要がありました。これは、gciでは-Excludeを使用し、他のコマンドでは|を使用して作成されました。そのため、ファイルを検索しただけの結果は少し異なる場合があります。

2
derBasti

これは、_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 *
}
_
0
Chris Rudd