根据PowerShell 脚本性能注意事项文档中的定义,repeatedly calling a function can be an expensive operation 。然而,相关函数(或只是代码)可能会在脚本中的多个位置(重新)使用,这会造成一个困境:
- 我应该创建一个安静昂贵的函数以更易于管理DRY代码,或者
- 我应该追求性能并在多个位置复制相关的代码块吗?
特别是如果相关代码块成本低廉但非常冗长。
用例示例
坚持性能目标,众所周知 using a hashtable as a lookup table安静可以带来不同。为此,您通常需要在创建查找表以及您将(尝试)检索哈希表保存的值的位置定义每个键。该 key 可能与所提供的一样。在我的特定情况下,我希望它比平常更符合 -eq
比较运算符。这对于我的用例来说意味着:
- ValueTypes 应转换为字符串以进行类似的类型转换(例如:
1 -eq '1'
和'1' -eq 1
) $Null
应该被接受,但不匹配任何内容(例如$Null -ne ''
),除了$Null
本身($Null -eq $Null
)- 对象应按值进行至少一级比较。
知道通常的对象键,例如像@{ @(1,2,3) = 'Test' }[@(1,2,3)]
这样的数组不会返回任何内容。
请注意,这个用例并不独立,还有很多其他情况,您可能会重用也在大型迭代中使用的函数。 (请注意, self 回答也是一个用例,希望在不产生额外费用的情况下重用相关代码块。)
选择
换句话说,我应该选择 DRY 代码吗:
Function Lookup {
param (
[Parameter(ValueFromPipeLine = $True)]$Item,
[Int]$Size = 100000
)
begin {
function Value2Key ($Value) { # Indexing
if ( $Null -eq $Value ) { "`u{1B}`$Null" }
elseif ($Value -is [ValueType]) { "$Value" }
elseif ($Value -is [System.MarshalByRefObject]) { "`u{1B}$($Value |Select-Object *)" }
elseif ($Value -is [System.Collections.IDictionary]) { "`u{1B}$($Value.GetEnumerator())" }
else { "`u{1B}$Value" }
}
$Hashtable = @{}
for ($Value = 0; $Value -lt $Size; $Value++) {
$Key = Value2Key $Value
$Hashtable[$Key] = "Some value: $Value"
}
}
process {
$Key = Value2Key $_
$Hashtable[$Key]
}
}
'DRY code = {0}ms' -f (Measure-Command -Expression { 3 |Lookup |Write-Host }).TotalMilliseconds
Some value: 3
DRY code = 5025.3474ms
或者我应该选择快速代码(对于 100.000 个项目,速度快了 10 倍以上):
Function Lookup {
param (
[Parameter(ValueFromPipeLine = $True)]$Item,
[Int]$Size = 100000
)
begin {
$Hashtable = @{}
for ($Value = 0; $Value -lt $Size; $Value++) { # Indexing
$Key =
if ( $Null -eq $Value ) { "`u{1B}`$Null" }
elseif ($Value -is [ValueType]) { "$Value" }
elseif ($Value -is [System.MarshalByRefObject]) { "`u{1B}$($Value |Select-Object *)" }
elseif ($Value -is [System.Collections.IDictionary]) { "`u{1B}$($Value.GetEnumerator())" }
else { "`u{1B}$Value" }
$Hashtable[$Key] = "Some value: $Value"
}
}
process {
$Value = $_
$Key =
if ( $Null -eq $Value ) { "`u{1B}`$Null" }
elseif ($Value -is [ValueType]) { "$Value" }
elseif ($Value -is [System.MarshalByRefObject]) { "`u{1B}$($Value |Select-Object *)" }
elseif ($Value -is [System.Collections.IDictionary]) { "`u{1B}$($Value.GetEnumerator())" }
else { "`u{1B}$Value" }
$Hashtable[$Key]
}
}
'Fast code = {0}ms' -f (Measure-Command -Expression { 3 |Lookup |Write-Host }).TotalMilliseconds
Some value: 3
Fast code = 293.3154ms
问题
正如用例所暗示的那样,我不关心调用代码的范围(当前范围或子范围)。
有没有更好或更快的方法来重用脚本中的静态代码块?
最佳答案
为了完整起见,添加 static class method 的性能测试与您的答案的 2 种最佳性能方法相比:
$tests = @{
'Hardcoded' = {
for ($Value = 0; $Value -lt $Repeat; $Value++) {
if ( $Null -eq $Value ) { "`u{1B}`$Null" }
elseif ($Value -is [ValueType]) { "$Value" }
elseif ($Value -is [System.MarshalByRefObject]) { "`u{1B}$($Value |Select-Object *)" }
elseif ($Value -is [System.Collections.IDictionary]) { "`u{1B}$($Value.GetEnumerator())" }
else { "`u{1B}$Value" }
}
}
'Static Method' = {
class Value2Key {
static [string] ToString([object] $Value) {
if ( $Null -eq $Value ) {
return "`u{1B}`$Null"
}
if ($Value -is [ValueType]) {
return "$Value"
}
if ($Value -is [System.MarshalByRefObject]) {
return "`u{1B}$($Value |Select-Object *)"
}
if ($Value -is [System.Collections.IDictionary]) {
return "`u{1B}$($Value.GetEnumerator())"
}
return "`u{1B}$Value"
}
}
for ($Value = 0; $Value -lt $Repeat; $Value++) {
[Value2Key]::ToString($Value)
}
}
'Steppable Pipeline' = {
function Value2Key {
param (
[Parameter(ValueFromPipeLine = $True)]$Value
)
process {
if ( $Null -eq $Value ) { "`u{1B}`$Null" }
elseif ($Value -is [ValueType]) { "$Value" }
elseif ($Value -is [System.MarshalByRefObject]) { "`u{1B}$($Value |Select-Object *)" }
elseif ($Value -is [System.Collections.IDictionary]) { "`u{1B}$($Value.GetEnumerator())" }
else { "`u{1B}$Value" }
}
}
$Pipeline = { Value2Key }.GetSteppablePipeline()
$Pipeline.Begin($True)
for ($Value = 0; $Value -lt $Repeat; $Value++) {
$Pipeline.Process($Value)
}
$Pipeline.End()
}
'Dot source codeblock without parameter' = {
$Value2Key = {
if ( $Null -eq $Value ) { "`u{1B}`$Null" }
elseif ($Value -is [ValueType]) { "$Value" }
elseif ($Value -is [System.MarshalByRefObject]) { "`u{1B}$($Value |Select-Object *)" }
elseif ($Value -is [System.Collections.IDictionary]) { "`u{1B}$($Value.GetEnumerator())" }
else { "`u{1B}$Value" }
}
for ($Value = 0; $Value -lt $Repeat; $Value++) {
. $Value2Key
}
}
}
$Repeat = 100000
$tests.GetEnumerator() | ForEach-Object {
[pscustomobject]@{
Test = $_.Key
Milliseconds = (Measure-Command { & $_.Value }).TotalMilliseconds
}
} | Sort-Object Milliseconds
结果与 Steppable Pipeline 非常相似:
Test Milliseconds
---- ------------
Hardcoded 52.52
Static Method 434.88
Steppable Pipeline 441.33
Dot source codeblock without parameter 1046.47
关于powershell - 重用静态代码的最佳方法是什么,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/75722846/