Powershell: Stringbuilder is slower then using native C# in Powershell

Created on 8 Jan 2020  路  9Comments  路  Source: PowerShell/PowerShell


Both versions use basically the same code, the stringbuilder.
Am i doing this the wrong way in powershell perhaps?
Or is it something else causing the slow performance.

As a side note, the same code in ps5.1 is way slower so at least some improvements have been made 馃憤

Steps to reproduce

# get any large file as bytes, mine is 1.5mb
$data=[system.io.file]::ReadAllBytes("c:\foo\bar.data")
$data.Length
1507431

# Slow version 
Function Convert-ByteArrayToHex {

    [cmdletbinding()]

    param(
        [parameter(Mandatory=$true)]
        [Byte[]]
        $Bytes
    )

    $HexString = [System.Text.StringBuilder]::new($Bytes.Length * 2)

    ForEach($byte in $Bytes){
        $HexString.AppendFormat("{0:x2}", $byte) | Out-Null
    }

    $HexString.ToString()
}
Measure-Command {
    $test = Convert-ByteArrayToHex -Bytes $data
}

# Fast version using C#
Function Convert-ByteArrayToHex {

    [cmdletbinding()]

    param(
        [parameter(Mandatory = $true)]
        [Byte[]]
        $Bytes
    )

    $code = @"
using System;
using System.Text;
namespace Builder
{
    public class String
    {
        public byte[] InBytes
        { get; set; }

        public string StringBuild()
        {
            StringBuilder sb = new StringBuilder(InBytes.Length * 2);
            foreach (byte b in InBytes)
            {
                sb.AppendFormat("{0:x2}", b);
            }
            return sb.ToString();
        }
    }
}
"@
    $Assem = (
        "System",
        "System.Collections"
    )

    if (-not ([System.Management.Automation.PSTypeName]'Builder.String').Type) {
        Add-Type -TypeDefinition $code -ReferencedAssemblies $Assem -Language CSharp
    }
    $StringBuilder = [Builder.String]::new()
    $StringBuilder.InBytes = $Bytes
    return $StringBuilder.StringBuild()
}

Expected behavior

Fast execution as the C# version has
Days              : 0
Hours             : 0
Minutes           : 0
Seconds           : 0
Milliseconds      : 204
Ticks             : 2043716
TotalDays         : 2,36541203703704E-06
TotalHours        : 5,67698888888889E-05
TotalMinutes      : 0,00340619333333333
TotalSeconds      : 0,2043716
TotalMilliseconds : 204,3716

Actual behavior

Much slower execution
Days              : 0
Hours             : 0
Minutes           : 0
Seconds           : 8
Milliseconds      : 498
Ticks             : 84980517
TotalDays         : 9,83570798611111E-05
TotalHours        : 0,00236056991666667
TotalMinutes      : 0,141634195
TotalSeconds      : 8,4980517
TotalMilliseconds : 8498,0517

Environment data

Name                           Value
----                           -----
PSVersion                      6.2.3
PSEdition                      Core
GitCommitId                    6.2.3
OS                             Microsoft Windows 10.0.18363
Platform                       Win32NT
PSCompatibleVersions           {1.0, 2.0, 3.0, 4.0鈥
PSRemotingProtocolVersion      2.3
SerializationVersion           1.1.0.1
WSManStackVersion              3.0
Issue-Question

All 9 comments

Out-null is very slow. Try this $null = $HexString.AppendFormat("{0:x2}", $byte)

Out-null is very slow. Try this $null = $HexString.AppendFormat("{0:x2}", $byte)

Thank you!
Very interesting, i didn't know Out-null was so slow.
It's indeed faster, fast enough perhaps.

Still it's ~3x slower then c# example, i can live with this however.

Days : 0
Hours : 0
Minutes : 0
Seconds : 0
Milliseconds : 706
Ticks : 7061125
TotalDays : 8,17259837962963E-06
TotalHours : 0,000196142361111111
TotalMinutes : 0,0117685416666667
TotalSeconds : 0,7061125
TotalMilliseconds : 706,1125

Out-null is very slow. Try this $null = $HexString.AppendFormat("{0:x2}", $byte)

Thank you!
Very interesting, i didn't know Out-null was so slow.
It's indeed faster, fast enough perhaps.

Still it's ~3x slower then c# example, i can live with this however.

Days : 0
Hours : 0
Minutes : 0
Seconds : 0
Milliseconds : 706
Ticks : 7061125
TotalDays : 8,17259837962963E-06
TotalHours : 0,000196142361111111
TotalMinutes : 0,0117685416666667
TotalSeconds : 0,7061125
TotalMilliseconds : 706,1125

I used on accident the wrong version of PS in vscode, with v6 it's ~2x difference between c# and PS

Guess I'll never use Out-Null again then.

Oh, out-null hasn't been significantly slow for quite a while now.

In v6 and above out-null should be about the same as other methods of discarding the unwanted output.

Ihard to say exactly where the slowdown comes, but PS will always be slightly slower than equivalent c#

Hm interesting.
When i use Out-Null -InputObject $HexString.AppendFormat("{0:x2}", $byte)
I get the same slow result as piping it to Out-Null.

Using [void]$HexString.AppendFormat("{0:x2}", $byte) is just as fast as $null =
And $HexString.AppendFormat("{0:x2}", $byte) > $null
Is almost as fast as [void] and $null =

I've seen that discussion quite a bit, haha. In most cases I usually see...

  1. Fastest: $null =
  2. > $null
  3. [void]
  4. Slowest: | Out-Null

They're usually pretty close though, with the exception of Out-Null being _much_ slower if you pipe to it on PS 5 and earlier.

I'm not sure if there's a difference between how output from PS functions and output from .NET methods behaves when applying [void] though.

Good to know, it would be fun to know what's causing Out-Null to be so amazingly slow or perhaps it's because of the pipeline being used.

But is this not avoided when using the -inputObject parameter?

The pipeline overhead is bypassed in 6.0+ from what I remember, an exception was added specifically for Out-Null.

I would imagine most of the rest of the extra time for Out-Null is likely spent in the parameter binder when it's called directly. 馃

Yes this can be seen when i use the same code in 5.1, it takes 48s instead of 8s :)

Was this page helpful?
0 / 5 - 0 ratings