As defined in the PowerShell scripting performance considerations document, repeatedly calling a function can be an expensive operation. Yet the concerned function (or just code) might be (re)used at several locations in a script, which leaves a dilemma:
- Should I create a quiet expensive function for a easier manageable DRY code, or
- Should I go for performance and copy the concerned piece of code block at several locations?
Especially if the concerned code block is inexpensive but very verbose.
Use case example
Sticking with the performance goal, it quiet known that using a hashtable as a lookup table could make quiet a difference. For this you will usually need to the define each key at the point you create the lookup table and where you would (try) retrieve the value hold by the hashtable. That key might as literal as it is provided. In my particular case, I want it more corresponding to the -eq
comparison operator than usual. This means for my use case:
- ValueTypes should be converted to strings to be type casting alike (e.g.:
1 -eq '1'
and'1' -eq 1
) $Null
should be accepted but not match anything (as e.g.$Null -ne ''
) except for$Null
itself ($Null -eq $Null
)- Objects should compare by value at least one level.
Knowing that normally object keys as e.g. an array like@{ @(1,2,3) = 'Test' }[@(1,2,3)]
don't return anything.
Note that this use case doesn't stand on it self, there are a lot of other situations were you might reuse a function that is also used in a large iteration. (note that the self answer is also a use case where would like to reuse the concerned codeblock without extra costs.)
Choice
In other words, should I go for the DRY code:
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
Or should I go for the fast code (which is more than 10 times faster for 100.000 items):
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
Question
As the used case implies, I don't care about which scope (current or child) the code is invoked.
Are there any better or faster ways to reuse static code blocks in a script?