7

I've been wondering about the performance impact of functions in PowerShell. Let's say we want to generate 100.000 random numbers using System.Random.

$ranGen = New-Object System.Random

Executing

for ($i = 0; $i -lt 100000; $i++) {
    $void = $ranGen.Next()
}

finishes within 0.19 seconds. I put the call inside a function

Get-RandomNumber {
    param( $ranGen )

    $ranGen.Next()
}

Executing

for ($i = 0; $i -lt 100000; $i++) {
    $void = Get-RandomNumber $ranGen
}

takes about 4 seconds.

Why is there such a huge performance impact?

Is there a way I can use functions and still get the performance I have with the direct call?

Are there better (more performant) ways of code encapsulation in PowerShell?

Ben
  • 137
  • 1
  • 7
  • I tried make function with strict input and output types (i.e. specify input parameter type as `System.Random` and output type as `int`). But this just added another second. ( – Max Jan 30 '19 at 06:47
  • if you don't pass `$ranGen` as a parameter, but use it directly, it saves a second. and if define `$global:ranGen` and use this global var in the function, it saves another second. – Max Jan 30 '19 at 06:52
  • I see no difference when I specify the parameter types. If I call $ranGen directly (or as a global variable), the code takes one second more. Weird. – Ben Jan 30 '19 at 07:32
  • As an aside: You can speed up the loop by replacing `for ($i=0; $i -lt 100000; $i++)` with `foreach ($i in 1..100000)` - perhaps surprisingly, creating an array of indices first, using the [range operator](https://docs.microsoft.com/en-us/powershell/module/microsoft.powershell.core/about/about_Operators#range-operator-) (`..`) , and looping over the array's elements with [`foreach`](https://docs.microsoft.com/en-us/powershell/module/microsoft.powershell.core/about/about_Foreach) is faster than a functionally equivalent `for` loop (albeit more memory-intensive, though that will rarely matter). – mklement0 Sep 20 '21 at 22:40

1 Answers1

11

a function call is expensive. The way to get around that is to put as much as you can IN the function. take a look at the following ...

$ranGen = New-Object System.Random
$RepeatCount = 1e4

'Basic for loop = {0}' -f (Measure-Command -Expression {
    for ($i = 0; $i -lt $RepeatCount; $i++) {
        $Null = $ranGen.Next()
    }
    }).TotalMilliseconds

'Core in function = {0}' -f (Measure-Command -Expression {
function Get-RandNum_Core {
    param ($ranGen)
    $ranGen.Next()
    }

    for ($i = 0; $i -lt $RepeatCount; $i++) {
        $Null = Get-RandNum_Core $ranGen
    }
    }).TotalMilliseconds

'All in function = {0}' -f (Measure-Command -Expression {
    function Get-RandNum_All {
        param ($ranGen)
        for ($i = 0; $i -lt $RepeatCount; $i++) {
            $Null = $ranGen.Next()
            }
        }
    Get-RandNum_All $ranGen
    }).TotalMilliseconds

output ...

Basic for loop = 49.4918
Core in function = 701.5473
All in function = 19.5579

from what i vaguely recall [and can't find again], after a certain number of repeats, the function scriptblock gets JIT-ed ... that seems to be where the speed comes from.

Lee_Dailey
  • 7,292
  • 2
  • 22
  • 26
  • Wow! Get-RanNum_All is a major performance improvement! Thanks, I didn't know that! I thought PowerShell was always JIT-ed. – Ben Jan 30 '19 at 10:09
  • @Ben - yep, it seems to be JIT-ed only when it gets repeated "often enuf" ... whatever that may be. [*grin*] – Lee_Dailey Jan 30 '19 at 10:18
  • 2
    16 times, according to this: https://stackoverflow.com/questions/34202462/does-powershell-compile-scripts – Janne Tuukkanen Jan 31 '19 at 08:15
  • 2
    @JanneTuukkanen - _thank you_! that is exactly the sort of info i was seeking ... [*grin*] – Lee_Dailey Jan 31 '19 at 17:57
  • 1
    See: [`#19322` Optimize static code reusage](https://github.com/PowerShell/PowerShell/discussions/19322) – iRon Mar 14 '23 at 09:21