Is there anything more that can be done to reduce the overhead of calling functions? #15729
Replies: 6 comments 15 replies
-
You are more likely to encounter worse performance on PS7. No improvements have been made in parameter bindings. |
Beta Was this translation helpful? Give feedback.
-
The fastest advisable way will probably be That said, something with using namespace System.Management.Automation
$script:value = 0L
$script:pipe = {
& {
[CmdletBinding()]
param(
[Parameter(ValueFromPipeline)]
[int64] $i
)
process {
$script:value += $i
}
}
}.GetSteppablePipeline([CommandOrigin]::Internal)
$script:pipe.Begin(<# expectingInput: #> $true)
for ($i = 0L; $i -lt 1000L; $i++) {
$script:pipe.Process($i)
} Also FWIW there's a huge amount of pit falls when benchmarking (especially very small benchmarks like these) in PowerShell. Dot sourcing, the scope you pull access variables from, and a whole lot of other factors can make something look like worse performance than it actually is in a real world script. |
Beta Was this translation helpful? Give feedback.
-
Has there been any suggestion and/or advancement in a MSIL JIT/compile routine for Powershell code? I saw PS2EXE, but that looks like it bundles a host and feeds your script to it, rather than compiling, per se... |
Beta Was this translation helpful? Give feedback.
-
So, it seems the route that I've gone ( Happy to open this up to ways in which further development of PowerShell could improve on the situation, especially in script/module code... |
Beta Was this translation helpful? Give feedback.
-
Try using the pipeline, and omit an explicit parameter block: [CmdletBinding()]
Param(
[Int64[]] $Iterations = (1000,10000,100000,1000000) #,10000000)
)
$value = 999
function func0 {
# this is the way
process {
$value += $_
}
}
function func0B {
param(
[parameter( mandatory=$true,ValueFromPipeline=$true)]
$x
)
process {
$value += $x
}
}
function script:func1 { # a standard function
Param([Int64]$n)
$script:value += $n
}
$script:func2 = { # a script block, which I can .Invoke() or .InvokeReturnAsIs()
Param([Int64]$n)
$script:value += $n
}
$script:func3 = [Action[Int64]] { # a script block coerced to an Action<>
Param([Int64]$n)
$script:value += $n
}
$cs = (Add-Type -Language CSharp -PassThru -TypeDefinition @"
public static class Lib_$([Guid]::NewGuid().ToString("N"))
{ // a c# function, which I can passthru and call directly
public static void DoIt(ref long orig, long n)
{
orig += n;
}
}
"@)[0]
$sbCsFuncPipe = `
{
process {
$cs::DoIt([ref]$script:value, $_ )
}
}
foreach ($script:iter in $Iterations) {
Write-Output (New-Object PSObject -Property @{
'Method' = 'Unrolled - make this wide ...'
'Iterations' = $script:iter
'Timing' = (Measure-Command {
[Int64] $script:value = 0L
foreach ($i in (1..$script:iter)) {
$script:value += $i # just don't call a function
}
})
})
Write-Output "value: $($script:value)"
Write-Output (New-Object PSObject -Property @{
'Method' = 'func zero'
'Iterations' = $script:iter
'Timing' = (Measure-Command {
[Int64] $script:value = 0L
$func = $function:func0
$thisMany = 1 .. $script:iter
# use the pipeline
$thisMany | . $func
# 1 .. $script.iter | . $func
})
})
Write-Output "value: $($script:value)"
Write-Output (New-Object PSObject -Property @{
'Method' = 'func zero B'
'Iterations' = $script:iter
'Timing' = (Measure-Command {
[Int64] $script:value = 0L
$func = $function:func0B
# use the pipeline, with function that has a named parameter
$thisMany = 1 .. $script:iter
$thisMany | . $func
# ( 1 .. $script.iter ) | . $func
})
})
Write-Output "value: $($script:value)"
Write-Output (New-Object PSObject -Property @{
'Method' = 'func zero B via x'
'Iterations' = $script:iter
'Timing' = (Measure-Command {
[Int64] $script:value = 0L
$func = $function:func0B
# use the pipeline, with function that has a named parameter,
# but via `ForEach-Object`, as if you didn't know that the
# function accepted pipeline input
$thisMany = 1 .. $script:iter
$thisMany | Foreach-Object { . $func -x $_ }
# ( 1 .. $script.iter ) | Foreach-Object { . $func -x $_ }
})
})
Write-Output "value: $($script:value)"
Write-Output (New-Object PSObject -Property @{
'Method' = 'csfunc'
'Iterations' = $script:iter
'Timing' = (Measure-Command {
[Int64] $script:value = 0L
foreach ($i in (1..$script:iter)) {
$cs::DoIt([ref]$script:value, $i)
}
})
})
Write-Output "value: $($script:value)"
Write-Output (New-Object PSObject -Property @{
'Method' = 'csfunc via pipeline'
'Iterations' = $script:iter
'Timing' = (Measure-Command {
[Int64] $script:value = 0L
# use the csharp function indirectly, via a powershell function
# that accepts pipeline input
$thisMany = 1 .. $script:iter
$thisMany | & $sbCsFuncPipe
})
})
Write-Output "value: $($script:value)"
Write-Output "-----------`n"
# Don't even bother with these:
# Write-Output (New-Object PSObject -Property @{
# 'Method' = 'func zero B via foreach'
# 'Iterations' = $script:iter
# 'Timing' = (Measure-Command {
# [Int64] $script:value = 0L
# $func = $function:func0B
# foreach ($i in (1..$script:iter)) {
# & $func $i
# }
# })
# })
# Write-Output (New-Object PSObject -Property @{
# 'Method' = 'func1C'
# 'Iterations' = $script:iter
# 'Timing' = (Measure-Command {
# [Int64] $script:value = 0L
# $func = $function:func1
# foreach ($i in (1..$script:iter)) {
# & $func $i
# }
# })
# })
# Write-Output (New-Object PSObject -Property @{
# 'Method' = 'func1'
# 'Iterations' = $script:iter
# 'Timing' = (Measure-Command {
# [Int64] $script:value = 0L
# foreach ($i in (1..$script:iter)) {
# script:func1($i)
# }
# })
# })
# Write-Output (New-Object PSObject -Property @{
# 'Method' = 'func1B'
# 'Iterations' = $script:iter
# 'Timing' = (Measure-Command {
# [Int64] $script:value = 0L
# foreach ($i in (1..$script:iter)) {
# script:func1 $i
# }
# })
# })
# Write-Output (New-Object PSObject -Property @{
# 'Method' = 'func2'
# 'Iterations' = $script:iter
# 'Timing' = (Measure-Command {
# [Int64] $script:value = 0L
# foreach ($i in (1..$script:iter)) {
# $script:func2.Invoke($i)
# }
# })
# })
# Write-Output (New-Object PSObject -Property @{
# 'Method' = 'func2-asis'
# 'Iterations' = $script:iter
# 'Timing' = (Measure-Command {
# [Int64] $script:value = 0L
# foreach ($i in (1..$script:iter)) {
# $script:func2.InvokeReturnAsIs($i)
# }
# })
# })
# Write-Output (New-Object PSObject -Property @{
# 'Method' = 'func3'
# 'Iterations' = $script:iter
# 'Timing' = (Measure-Command {
# [Int64] $script:value = 0L
# foreach ($i in (1..$script:iter)) {
# $script:func3.Invoke($i)
# }
# })
# })
} |
Beta Was this translation helpful? Give feedback.
-
Parameter binding area is very complex and sensitive. That's the price of versatility. The current implementation of parameter bindings is well optimized. It is unlikely that we can do anything better there directly.
Unfortunately, we do not have community experts who would implement improvements in this area. |
Beta Was this translation helpful? Give feedback.
-
Considering the basest of contrived examples, below:
...the results of this may be surprising:
My guess is that the woeful performance of
func1
is probably due to parameter binding but---at least to me---the overhead of calling a "normal" function is disappointing, especially after seeing a concerted effort to make a larger script more readable plummett performance by as much as 20x -- significantly more if there were multiple function calls per iteration.Appreciating that rewriting the entire script in C# is...less than ideal...are there any (preferably backward-compatible) approaches, in general, that I have missed which might improve upon the
func2
/func3
best-case pure-Powershell approaches?I'm personally stuck with running in Win PS -- readings were taken from Win10/PS5.1 -- I can't shift my current stack away from WinPS at the moment but, for knowledge's sake, is there anything in PS6/7+ that significantly improves on this?
Or is splitting work into readable functions just a write-off?
Beta Was this translation helpful? Give feedback.
All reactions