powershell 重用静态代码的最佳方法是什么

relj7zay  于 2023-03-30  发布在  Shell
关注(0)|答案(3)|浏览(157)

正如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

问题

正如用例所暗示的,我不关心调用代码的作用域(当前或子)。
是否有更好或更快的方法来重用脚本中的静态代码块?

ubbxdtey

ubbxdtey1#

为了完成,添加静态类方法的性能测试,与您的答案中的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

结果与“可步进管道”非常相似:

Test                                   Milliseconds
----                                   ------------
Hardcoded                                     52.52
Static Method                                434.88
Steppable Pipeline                           441.33
Dot source codeblock without parameter      1046.47
nnsrf1az

nnsrf1az2#

在用例示例中,相关代码块为:

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" }

下面,我测试了几种调用特定代码的方法的性能:

$Repeat = 10000

[PSCustomObject]@{ Method = 'Invoke-Command with parameter'; 'Time (ms)' = (Measure-Command -Expression {
    $Value2Key = {
        param($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" }
    }
    for ($Value = 0; $Value -lt $Repeat; $Value++) {
        $Key = Invoke-Command $Value2Key -ArgumentList $Value
    }
}).TotalMilliseconds }
$Key |Should -be "$($Repeat - 1)"

[PSCustomObject]@{ Method = 'Invoke-Command without parameter'; 'Time (ms)' = (Measure-Command -Expression {
    $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++) {
        $Key = Invoke-Command $Value2Key
    }
}).TotalMilliseconds }
$Key |Should -be "$($Repeat - 1)"

[PSCustomObject]@{ Method = 'Advanced function'; 'Time (ms)' = (Measure-Command -Expression {
    function Value2Key {
        Param($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" }
    }
    for ($Value = 0; $Value -lt $Repeat; $Value++) {
        $Key = Value2Key $Value
    }
}).TotalMilliseconds }
$Key |Should -be "$($Repeat - 1)"

[PSCustomObject]@{ Method = 'Simple function with parameter'; 'Time (ms)' = (Measure-Command -Expression {
    function Value2Key ($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" }
    }
    for ($Value = 0; $Value -lt $Repeat; $Value++) {
        $Key = Value2Key $Value
    }
}).TotalMilliseconds }
$Key |Should -be "$($Repeat - 1)"

[PSCustomObject]@{ Method = 'Simple function without parameter'; 'Time (ms)' = (Measure-Command -Expression {
    function 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++) {
        $Key = Value2Key
    }
}).TotalMilliseconds }
$Key |Should -be "$($Repeat - 1)"

[PSCustomObject]@{ Method = 'Call codeblock with parameter'; 'Time (ms)' = (Measure-Command -Expression {
    $Value2Key = {
        param($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" }
    }
    for ($Value = 0; $Value -lt $Repeat; $Value++) {
        $Key = & $Value2Key $Value
    }
}).TotalMilliseconds }
$Key |Should -be "$($Repeat - 1)"

[PSCustomObject]@{ Method = 'Call codeblock without parameter'; 'Time (ms)' = (Measure-Command -Expression {
    $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++) {
        $Key = & $Value2Key
    }
}).TotalMilliseconds }
$Key |Should -be "$($Repeat - 1)"

[PSCustomObject]@{ Method = 'Dot source codeblock with parameter'; 'Time (ms)' = (Measure-Command -Expression {
    $Value2Key = {
        param($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" }
    }
    for ($Value = 0; $Value -lt $Repeat; $Value++) {
        $Key = . $Value2Key $Value
    }
}).TotalMilliseconds }
$Key |Should -be "$($Repeat - 1)"

[PSCustomObject]@{ Method = 'Dot source codeblock without parameter'; 'Time (ms)' = (Measure-Command -Expression {
    $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++) {
        $Key = . $Value2Key
    }
}).TotalMilliseconds }
$Key |Should -be "$($Repeat - 1)"

[PSCustomObject]@{ Method = 'Class'; 'Time (ms)' = (Measure-Command -Expression {
    class Value2Key {
        [String]$Key
        Value2Key($Value) {
            $This.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" }
        }
    }
    for ($Value = 0; $Value -lt $Repeat; $Value++) {
        $Key = ([Value2Key]$Value).Key
    }
}).TotalMilliseconds }
$Key |Should -be "$($Repeat - 1)"

[PSCustomObject]@{ Method = 'Steppable Pipeline'; 'Time (ms)' = (Measure-Command -Expression {
    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++) {
        $Key = $Pipeline.Process($Value)
    }
    $Pipeline.End()
}).TotalMilliseconds }
$Key |Should -be "$($Repeat - 1)"

[PSCustomObject]@{ Method = 'Hardcoded'; 'Time (ms)' = (Measure-Command -Expression {
    for ($Value = 0; $Value -lt $Repeat; $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" }
    }
}).TotalMilliseconds }
$Key |Should -be "$($Repeat - 1)"

这就是结果:

Method                                 Time (ms)
------                                 ---------
Invoke-Command with parameter            1581.06
Invoke-Command without parameter          992.65
Advanced function                         377.98
Simple function with parameter            326.36
Simple function without parameter         304.04
Call codeblock with parameter             273.72
Call codeblock without parameter          258.07
Dot source codeblock with parameter       301.57
Dot source codeblock without parameter    201.94
Class                                     108.79
Steppable Pipeline                         85.54
Hardcoded                                  35.17

事实证明,使用steppable pipeline是最快的方法(除了WET -Write Everything Twice- 硬编码解决方案)。不幸的是,创建可步进管道的开销并没有增加太多清晰的代码。
除此之外,我希望能够以某种方式重用静态代码,而(几乎)没有任何额外的成本。

chy5wohz

chy5wohz3#

不要忘记在脚本块上使用.Invoke()方法。

[PSCustomObject]@{ Method = 'Invoke codeblock with parameter'; 'Time (ms)' = (Measure-Command -Expression {
    $Value2Key = {
        param($Value)
        if ( $Null -eq $Value ) { "`$Null" }
        elseif ($Value -is [ValueType]) { "$Value" }
        elseif ($Value -is [System.MarshalByRefObject]) { "$($Value |Select-Object *)" }
        elseif ($Value -is [System.Collections.IDictionary]) { "$($Value.GetEnumerator())" }
        else { "$Value" }
    }
    for ($Value = 0; $Value -lt $Repeat; $Value++) {
        $Key =$Value2Key.Invoke($Value)
    }
}).TotalMilliseconds }

[PSCustomObject]@{ Method = 'Invoke codeblock without parameter'; 'Time (ms)' = (Measure-Command -Expression {
    $Value2Key = {
        if ( $Null -eq $Value ) { "`$Null" }
        elseif ($Value -is [ValueType]) { "$Value" }
        elseif ($Value -is [System.MarshalByRefObject]) { "$($Value |Select-Object *)" }
        elseif ($Value -is [System.Collections.IDictionary]) { "$($Value.GetEnumerator())" }
        else { "$Value" }
    }
    for ($Value = 0; $Value -lt $Repeat; $Value++) {
        $Key = $Value2Key.Invoke()
    }
}).TotalMilliseconds }

运行时间:

Method                             Time (ms)
------                             ---------
Invoke codeblock with parameter       107.65
Invoke codeblock without parameter     82.95

这些比点源脚本块更快,更接近可步进管道或类版本。

相关问题