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

标签 powershell performance invoke

根据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/

相关文章:

c# - MethodInfo.Invoke 性能问题

java - 使用 Java 反射 Junit4 测试异常

variables - 启动时使用 Powershell 脚本的参数

Powershell - 删除 "API running..."

linux - 如何通过性能测试中性能低的机器来计算算法在高性能机器上的性能?

针对完全静态数据库的 MySQL 优化技巧?

c++ - 为什么这个 Rcpp 代码比字节编译的 R 慢?

azure - 通过 Azure DevOps Pipeline 对 Azure DevOps REST API 进行 token 身份验证(而不是 PAT token )

c# - System.Management.Automation 与 .NET Core 3.1 不兼容

java - 我想打印 hi GrandFather;但它似乎打印 hi Father