正如PowerShell脚本性能注意事项文档中所定义的,重复调用一个函数可能是一个代价高昂的操作。然而,相关函数(或代码)可能会在脚本中的多个位置(重复)使用,这就带来了一个难题:
- 我应该为更易于管理的DRY代码创建一个代价很高的函数吗?
- 我是否应该为了性能而在多个位置复制相关的代码块?
特别是如果相关的代码块很便宜但非常冗长。
用例示例
坚持性能目标,众所周知,使用哈希表作为查找表可能会有很大的不同。为此,您通常需要在创建查找表时定义每个键,以及您将在何处定义。(尝试)检索哈希表保存的值。该键可能与提供的值一样字面。在我的特定情况下,我希望它比通常更符合-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
问题
正如用例所暗示的,我不关心调用代码的作用域(当前或子)。
是否有更好或更快的方法来重用脚本中的静态代码块?
3条答案
按热度按时间ubbxdtey1#
为了完成,添加静态类方法的性能测试,与您的答案中的2个最佳性能方法进行比较:
结果与“可步进管道”非常相似:
nnsrf1az2#
在用例示例中,相关代码块为:
下面,我测试了几种调用特定代码的方法的性能:
这就是结果:
事实证明,使用steppable pipeline是最快的方法(除了WET -Write Everything Twice- 硬编码解决方案)。不幸的是,创建可步进管道的开销并没有增加太多清晰的代码。
除此之外,我希望能够以某种方式重用静态代码,而(几乎)没有任何额外的成本。
chy5wohz3#
不要忘记在脚本块上使用
.Invoke()
方法。运行时间:
这些比点源脚本块更快,更接近可步进管道或类版本。