我的第一個陣列是寵物主人及其手機號碼等的串列。第二個陣列是帶有主人姓名資訊的寵物。寵物串列大約是 1k,所有者串列是 3k。
將所有者資訊添加到寵物資訊中的最快方法是什么?目前我的腳本需要將近一分鐘才能運行,這似乎有點太多了。
foreach ($pet in $pets) {
$owner = $owners | Where-Object { $_.name -eq $pet.owner }
if ($owner) {
$pet | Add-Member -MemberType NoteProperty -Name "Owner" -Value $owner.name }
}
uj5u.com熱心網友回復:
為每只寵物在所有者陣列中進行線性查找會使您的方法從根本上變慢:對于每只寵物,必須搜索 3,000 個所有者物件,導致 1,000 x 3,000 = 300 萬次查找。
性能還會受到您所做的附帶實施選擇的影響:
與 PowerShell管道一樣強大和優雅,它的逐一流式傳輸通常比在運算式/語言陳述句(例如回圈)中迭代陣列要慢得多。
foreach此外,從 PowerShell 7.2.2 開始,
Where-Object和ForEach-Objectcmdlet 的實作效率低下,這增加了額外的開銷 - 請參閱GitHub 問題 #10982和此答案。另一個使性能惡化的功能限制是一旦找到匹配項就
Where-Object無法停止列舉;也就是說,輸入總是被完整處理,并且所有匹配都被輸出。相比之下,類似的
.Where()陣列方法 確實提供了一種在找到第一個匹配項后停止處理的方法(例如, . 可能為cmdlet(1..10).Where({ $_ -ge 5 }, 'First')帶來相同的功能是GitHub 問題 #13834的主題。Where-Object
因此,您有兩個選擇:
(A) 務實的解決方案:堅持從根本上低效的方法,但提高實施效率,以便最終的性能可能足夠好:
- 下面的 (A) 解決方案比您原來
Where-Object的基于 - 的方法要快得多,大約快 33 到 38 倍,具體取決于所使用的 PowerShell 版本;有關基準,請參閱下一節。
- 下面的 (A) 解決方案比您原來
(B) 適當的、可擴展的、性能更好但更復雜的解決方案:使用輔助。如Darin所建議的,可以按名稱有效查找所有者物件的資料結構,例如hashtable。
- 下面的 (B) 解決方案比 (A) 解決方案快 7 到 13 倍,具體取決于所使用的 PowerShell 版本,因此 ca. 比解決方案快 260 到 420 (!) 倍
Where-Object;有關基準,請參閱下一節。
- 下面的 (B) 解決方案比 (A) 解決方案快 7 到 13 倍,具體取決于所使用的 PowerShell 版本,因此 ca. 比解決方案快 260 到 420 (!) 倍
筆記:
在下面的代碼中,我修改了您的示例,以便將所有者名稱以外的屬性作為新屬性添加到每個寵物物件 ( , as ),前提是所有者名稱以開頭存在。
.Address.OwnerAddress此外,為簡潔起見,呼叫中
-MemberType NoteProperty的-Name和-Value引數名稱Add-Member被省略(它們是隱含的)。
解決方案(A)Where-Object :用(內部)foreach陳述句替換管道:
# Create 1000 sample pets and 3000 sample owners.
$pets = foreach ($i in 1..1000) { [pscustomobject] @{ Name = "Pet $i"; Owner = 'Owner {0}' -f (6 * $i) } }
$owners = foreach ($i in 1..3000) { [pscustomobject] @{ Name = "Owner $i"; Address = "Address $i" } }
foreach ($pet in $pets) {
# Perform the lookup more efficiently via an inner `foreach` loop.
$owner = foreach ($o in $owners) { if ($o.Name -eq $pet.Owner) { $o; break } }
if ($owner) {
Add-Member -InputObject $pet OwnerAddress $owner.Address
}
}
解決方案 (B):創建一個將所有者名稱映射到所有者物件的哈希表,以實作高效查找:
# Create 1000 sample pets and 3000 sample owners.
$pets = foreach ($i in 1..1000) { [pscustomobject] @{ Name = "Pet $i"; Owner = 'Owner {0}' -f (6 * $i) } }
$owners = foreach ($i in 1..3000) { [pscustomobject] @{ Name = "Owner $i"; Address = "Address $i" } }
# Create a hashtable that maps owner names to owner objects,
# for efficient lookup by name.
$ownerMap = @{}; foreach ($owner in $owners) { $ownerMap[$owner.Name] = $owner }
foreach ($pet in $pets) {
# Look up the pet's owner in the owner map (hashtable); returns $null if not found.
$owner = $ownerMap[$pet.Owner]
if ($owner) {
Add-Member -InputObject $pet OwnerAddress $owner.Address
}
}
基準
以下是比較三種方法的示例時序,平均超過 10 次運行。
定時命令在 PowerShell 中從來都不是一門精確的科學,性能會根據許多因素而變化,尤其是硬體相對于絕對時間,但下面的結果提供了相對性能的感覺,如
Factor輸出列中所反映的:1.00表示最快的命令,首先列出,較慢的表示為它的倍數,按速度降序排列。底部部分包含源代碼,允許您自己運行這些基準測驗
警告:在給定的集合大小下,這些基準測驗會運行相當長的一段時間(最多 10 分鐘或更長時間),主要是由于
Where-Object解決方案的速度太慢。為了獲得最佳結果,請在您的機器不(太)忙于做其他事情時運行基準測驗。
請注意,相對于 Windows PowerShell,跨平臺PowerShell (Core) 版本的整體性能似乎有了顯著提高。
Windows 10 上的 Windows PowerShell 5.1:
Factor Secs (10-run avg.) Command
------ ------------------ -------
1.00 0.234 # Hashtable-assisted lookups....
6.85 1.605 # Nested foreach statements...
261.95 61.353 # Pipeline with Where-Object...
Windows 10 上的 PowerShell(核心)7.2.2:
Factor Secs (10-run avg.) Command
------ ------------------ -------
1.00 0.096 # Hashtable-assisted lookups.…
12.70 1.216 # Nested foreach statements…
424.40 40.624 # Pipeline with Where-Object…
基準源代碼:
以下基準代碼使用
Time-Command此Gist中的函式。除非已經存在,否則系統會提示您自動下載并在會話中定義此函式。(我個人可以向您保證,這樣做是安全的,但您應該始終自己檢查源代碼。)
# Download and define function `Time-Command` on demand (will prompt).
# To be safe, inspect the source code at the specified URL first.
if (-not (Get-Command -ErrorAction Ignore Time-Command)) {
$gistUrl = 'https://gist.github.com/mklement0/9e1f13978620b09ab2d15da5535d1b27/raw/Time-Command.ps1'
if ((Read-Host "`n====`n OK to download and define benchmark function ``Time-Command```n from Gist ${gistUrl}?`n=====`n(y/n)?").Trim() -notin 'y', 'yes') { Write-Warning 'Aborted.'; exit 2 }
Invoke-RestMethod $gistUrl | Invoke-Expression
if (-not ${function:Time-Command}) { exit 2 }
}
# Define the collection sizes
$petCount = 1000
$ownerCount = 3000
# Define a sample owners array.
$owners = foreach ($i in 1..$ownerCount) { [pscustomobject] @{ Name = "Owner $i"; Address = "Address $i" } }
# Define a script block that creates a sample pets array.
# Note: We use a script block, because the array must be re-created
# for each run, since the pet objects get modified.
$petGenerator = {
foreach ($i in 1..$petCount) { [pscustomobject] @{ Name = "Pet $i"; Owner = 'Owner {0}' -f (6 * $i) } }
}
# Define script blocks with the commands to time.
$commands = @(
{ # Nested foreach statements
$pets = & $petGenerator
foreach ($pet in $pets) {
$owner = foreach ($o in $owners) { if ($o.Name -eq $pet.Owner) { $o; break } }
if ($owner) {
Add-Member -ea stop -InputObject $pet OwnerAddress $owner.Address
}
}
},
{ # Pipeline with Where-Object
$pets = & $petGenerator
foreach ($pet in $pets) {
$found = $owners | Where-Object { $_.name -eq $pet.Owner }
if ($found) {
Add-Member -InputObject $pet OwnerAddress $owner.Address
}
}
},
{ # Hashtable-assisted lookups.
$pets = & $petGenerator
$ownerMap = @{}; foreach ($owner in $owners) { $ownerMap[$owner.Name] = $owner }
foreach ($pet in $pets) {
$owner = $ownerMap[$pet.Owner]
if ($owner) {
Add-Member -InputObject $pet OwnerAddress $owner.Address
}
}
}
)
Write-Verbose -Verbose 'Running benchmarks...'
# Average 10 runs.
# Add -OutputToHost to print script-block output, if desired.
Time-Command -Count 10 $commands
轉載請註明出處,本文鏈接:https://www.uj5u.com/houduan/466239.html
標籤:电源外壳
