Cmdlets or Advanced Functions?

I’ve written quite a few Advanced Functions for the PowerShell community over the past year. It’s awesome to be able to produce the behavior of a Cmdlet using syntax that the people using your module will be familar with. If you download one of a function or Script Module and don’t quite like how it works, you can just open up the file and make changes yourself.

However, this convenience comes at a performance cost. Even the most optimized PowerShell code will execute much, much slower than the equivalent compiled Cmdlet. For those people who need to process very large data sets in their PowerShell scripts, this performance difference might be a make-or-break factor.

I’d love to see what the PowerShell community feels about this. If you were to download a third-party PowerShell module, would you prefer to have one with compiled Cmdlets for the best performance (assuming that the source code was available), or do you feel more comfortable sticking with PowerShell Script Modules / Advanced Functions? Does the source or author of the module affect that decision?

About Dave Wyatt

Microsoft MVP (PowerShell), IT professional.
This entry was posted in PowerShell and tagged , , , , , . Bookmark the permalink.

14 Responses to Cmdlets or Advanced Functions?

  1. Pingback: Cmdlets or Advanced Functions? | PowerShell.org

  2. Can’t do c#…. so i guess i have to go with Adv function 🙂

    Like

  3. Hey Dave,
    Nice idea for a poll. I answered that I don’t care, but that answer changes if the question is how do I want to develop modules. I’m fine working either in C# or PowerShell and the modules I use can be either, but I mainly develop modules in PowerShell.

    Like

  4. It all depends. If you wrote something that has a very clearly-defined scope and is not liable to change very often if at all, then I’d say Cmdlets are acceptable. Also if you’re packaging a bunch of them as a “boxed product” like “Dave’s Amazing Powershell Module That Does Everything v1.0” you’d probably want to compile the Cmdlets. But if you’re writing sysadmin tools out in the field that other admins will need to look at, tweak, and modify, then you’d probably better keep it as a Powershell function. I find this to be the case almost all the time. There are already enough methods of achieving parallelism in Powershell that I rarely find myself concerned with speed. Sure if I wrote the thing in C++ I could see it done 89 microseconds faster… but if 89 microseconds concerns you, Powershell is probably not the right tool for that job in the first place.

    Like

    • Dave Wyatt says:

      You’re underestimating the performance gain that C# cmdlets have over the equivalent PowerShell code. The difference for a single statement may not seem very large, but when you have a function that accepts arbitrarily large input (usually via the pipeline), it really adds up. Try the following code as a simple test of this:

      Add-Type -TypeDefinition @'
          using System;
          using System.Text;
      
          public static class PerformanceTest
          {
              public static void ManyStringBuilders(uint iterations)
              {
                  for (uint i = 0; i < iterations; i++)
                  {
                      StringBuilder sb = new StringBuilder();
      
                      for (int j = (int)'A'; j <+ (int)'Z'; j++)
                      {
                          sb.Append((char)j);
                      }
                  }
              }
          }
      '@
      
      $iterations = 100000
      
      Measure-Command {
          for ($i = 0; $i -lt $iterations; $i++)
          {
              $sb = New-Object System.Text.StringBuilder
      
              for ($j = [int][char]'A'; $j -le [int][char]'Z'; $j++)
              {
                  $null = $sb.Append([char]$j)
              }
          }
      }
      
      Measure-Command { [PerformanceTest]::ManyStringBuilders($iterations) }
      

      On my computer, the C# function finishes in about 17 milliseconds, and the PowerShell loop takes about 12.5 seconds to complete, nearly 750 times slower than the C# version. The performance difference varies based on what statements you're calling in the PowerShell code, but the additional overhead of PowerShell code is always there (resolving properties and methods at runtime with Reflection, performing type conversions, etc.)

      Like

      • Derp McDerperson says:

        As I mentioned in this PowerShell bug:

        https://connect.microsoft.com/PowerShell/feedback/details/872690/add-averaging-feature-to-measure-command

        Measure-Command is useless because PowerShell runs in 2 modes, interpreted and compiled.

        The problem with your code is that it’s running in interpreted mode for a number of reasons (as a basic rule of thumb: [CmdletBinding()] and dot scoping run in interpreted mode).

        Simply just enclosing the stuff inside your Measure-Command with & triggers compiled mode and halves the execution time:

        Measure-Command {
        }
        }
        }

        You pretty much have to look at PowerShell in ILSpy to figure out the rules for triggering the 2 modes and what operations are slow and which are fast for each mode.

        I’ve modified your code to make it not only run in compiled mode but used various tricks to replace the slow ops for faster ones:

        $iterations = 100000

        Measure-Command {
        }
        }
        }

        runs in 740ms on my system. Not as good as the C# version, but not bad enough to justify using C# to speed it up.

        Like

        • Derp McDerperson says:

          Meh, wordpress ate my code. The one that runs in compiled mode should be:

          Measure-Command {
          	& {
          		for ($i = 0; $i -lt $iterations; $i++) {
          			$sb = New-Object System.Text.StringBuilder
          
          			for ($j = [int][char]'A'; $j -le [int][char]'Z'; $j++) {
          				$null = $sb.Append([char]$j)
          			}
          		}
              }
          }
          

          and the 740ms one should be:

          $iterations = 100000
          
          Measure-Command {
          	& {
          		[System.Text.StringBuilder]$sb = $null
          		[char]$a = 0
          		foreach ($null in 0..$iterations) {
          			$sb = ''
          
          			foreach ($a in 65..90) {
          				$null = $sb.Append($a)
          			}
          		}
          	}
          }
          

          Like

          • Dave Wyatt says:

            That’s pretty cool! The C# version is still about 30 times faster, but that’s still an impressive difference, getting a 12-second result down to under a second with just a few changes.

            I’ve tinkered with the code a bit, and the biggest wins in your changes seem to be the &{} syntax within the Measure-Command block, and replacing the call to New-Object with a cast from string to StringBuilder. Just making these two changes gets the execution time down to about 700ms on my computer, with your full optimized version clocking in at 500-ish.

            Measure-Command {
                & {
                    for ($i = 0; $i -lt $iterations; $i++) {
                        #$sb = New-Object System.Text.StringBuilder
                        $sb = [System.Text.StringBuilder]''
             
                        for ($j = [int][char]'A'; $j -le [int][char]'Z'; $j++) {
                            $null = $sb.Append([char]$j)
                        }
                    }
                }
            }
            

            Like

  5. I am a DotNet C# developer originally who wandered off to ALM, DevOps and of course PowerShell and I’d like to state:

    “Use Functions, unless…”

    The stuff I usally build is primarily meant for IT Pro guys like administrators, and they like readable functions. They can tinker and tweak the scripts if and when they want.
    When one releases a product that needs PowerShelll support or priority for performance, I would opt for C# based CmdLet DLL.

    Like

  6. Larry Weiss says:

    I can imagine a PowerShell compiler that creates a Cmdlet from an Advanced Function. Hopefully available soon from someone.

    Like

  7. Adam says:

    Figure its worth a response here. I love coding but try to avoid C# because none of my colleagues know it. If it were just me, i’d learn C# and use it… but I always get worried when I need C# in my scripts… Stops my colleagues picking it up!

    Like

Leave a comment