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
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 { $_ }
}
さて、これは私がそれをしている方法です、そしてそれはうまくいくようです。
$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 DIR
はGetFiles
よりもわずかに高速になります。次に、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)
大量のファイル(〜190.000)を使用して、提案された方法のいくつかを試しました。カイルのコメントで述べたように、GetFiles
はほぼ永久に必要なので、ここではあまり役に立ちません。
私の最初のテストでは、cmddirはGet-ChildItems
よりも優れていましたが、-Force
パラメーターを使用すると、GCIの速度が大幅に向上するようです。これにより、必要な時間はcmddirの場合とほぼ同じになりました。
追伸:私の場合、拡張子が原因でほとんどのファイルを除外する必要がありました。これは、gciでは-Exclude
を使用し、他のコマンドでは|
を使用して作成されました。そのため、ファイルを検索しただけの結果は少し異なる場合があります。
これは、_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 *
}
_