Nicolas Stefaniuk
Nicolas Stefaniuk

Reputation: 248

Call to custom function is very slow

I have a newbie question on powershell (v4).

Given the following code,

If I change content of function DoNothing to do something, the time doesn't extend much.

Obviously I have mad a big mistake somewhere but I can't see where it is. Thanks a lot for helping me understanding my mistake.

    function DoNothing($val) 
{
    return $val
}

Write-Host "Script started..."
$elapsed = [System.Diagnostics.Stopwatch]::StartNew() 


for($i=1
     $i -le 100000
     $i++){
     #$t = $i
     #$t = DoNothing($i)
     $t = DoNothing $i
     }

Write-Host "Script complete."
Write-Host "Total Elapsed Time: $($elapsed.Elapsed.ToString())"

Upvotes: 3

Views: 1060

Answers (2)

Nicolas Stefaniuk
Nicolas Stefaniuk

Reputation: 248

For information, in Python, the same thing (using function DoNothing) takes 0.01s. I don't know the main differences between Python and Powershell function handling but 0.01s vs 11.5s is 1k times faster...

import datetime

def DoNothing(val):
    return val

print("Script started...")
elapsed = datetime.datetime.now()


for i in range (1,100000):
    #t = i
    t = DoNothing(i)

print("Script complete.")
print('Total Elapsed Time: '+str(datetime.datetime.now() - elapsed))

Upvotes: 0

james
james

Reputation: 732

Good question. I believe this is just the performance overhead of making function calls in PowerShell, but I did some experimentation to check.

A few things I noticed:

1) Saving the file increased performance around 75% compared to just working out of the PowerShell ISE without saving the file (From 7.3 seconds to 4.3 seconds).

2) Turning DoNothing into a parameterless function improved the execution time by an additional ~25% (From 4.3 seconds to 3.3 seconds). This would be useful if creating $i as a global variable would save time, but I tested that as well and, sadly, it increased execution time to 4.7 seconds.

3) I thought maybe explicitly requesting $val to be passed in as an int would decrease execution time, but it didn't. Time increased by about 0.2 seconds.

4) Naming the -val parameter when calling DoNothing ($t = DoNothing -val $i) did not improve performance.

5) Using $val instead of return $val did not improve performance.

6) Using -lt instead of -le did not improve performance.

7) Adding DoNothing to a PS module with only the DoNothing function in it severely decreased performance (from 4.3 seconds to 15 seconds).

So, I think this is all due to function overhead. I don't see anything 'wrong' in your code.

This has been an interesting experiment and might change when I choose to use PowerShell functions in the future. I wonder how this compares to other scripting languages.

Out of curiosity, I ran these same operations using C# and the whole thing completed in one one-thousandth of a second. Code is below.

class Program
{
    private static int DoNothing(int val) {
        return val;
    }
    static void Main(string[] args)
    {
        System.Diagnostics.Stopwatch watch = System.Diagnostics.Stopwatch.StartNew();
        for (int i = 1; i <= 100000; i++)
        {
            int t = DoNothing(i);
        }
        Console.WriteLine(watch.Elapsed.ToString());
        Console.ReadKey();
    }
}

Now I've gone a bit further than this. I figure if you really needed this to be faster, maybe you could throw the workload out to C# from PowerShell. This ended up being MUCH MUCH FASTER than the initial implementation in PowerShell, but slower than the C# only option. This code runs in ~ 0.03 seconds. See the code below.

PowerShell (calling the C#)

$elapsed = [System.Diagnostics.Stopwatch]::StartNew()
Write-Host "Script started..."
$lib = [Reflection.Assembly]::LoadFile("C:\Users\you\source\ClassLibrary1\bin\Debug\ClassLibrary1.dll")
$type = $lib.GetType("ClassLibrary1.Class1")
$method = $type.GetMethod("MyFunction")
$o = [Activator]::CreateInstance($type)
$method.Invoke($o, $null)
Write-Host "Script complete."
Write-Host "Total Elapsed Time: $($elapsed.Elapsed.ToString())"

C# (doing the work)

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace ClassLibrary1
{
    public class Class1
    {
        public static int DoNothing(int val)
        {
            return val;
        }

        public static void MyFunction()
        {
            for (int i = 1; i <= 100000; i++)
            {
                int t = DoNothing(i);
            }
        }
    }
}

Upvotes: 5

Related Questions