Replace characters in a string using Vectorization

 
 
  • Gérald Barré

This post is part of the series 'SIMD'. Be sure to check out the rest of the blog posts of the series!

I continue to look at code that uses Vectorization in the .NET libraries. In this post, we'll check the method ReplacePlusWithSpace from the ASP.NET Core code. This method replaces + with (space). This is useful to unescape URLs. This method uses Vector128 and SSE2 instructions. Like in previous posts, the comments in the code are mine:

C#
// source: https://github.com/dotnet/aspnetcore/blob/c65dac77cf6540c81860a42fff41eb11b9804367/src/Shared/QueryStringEnumerable.cs#L169

// Cache the delegate to avoid an instantiation each time, or a null check
// https://www.meziantou.net/performance-lambda-expressions-method-groups-and-delegate-caching.htm
private static readonly SpanAction<char, IntPtr> s_replacePlusWithSpace = ReplacePlusWithSpaceCore;

[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static unsafe string ReplacePlusWithSpace(ReadOnlySpan<char> span)
{
    // You cannot use the ReadOnlySpan<char> as a parameter of string.Create<T>().
    // The following code doesn't compile:
    // string.Create<ReadOnlySpan<char>>(10, span, (buffer, span) => { });
    //
    // The workaround is to create a pointer from the ReadOnlySpan and use the pointer
    // in string.Create
    fixed (char* ptr = &MemoryMarshal.GetReference(span))
    {
        return string.Create(span.Length, (IntPtr)ptr, s_replacePlusWithSpace);
    }
}

private static unsafe void ReplacePlusWithSpaceCore(Span<char> buffer, IntPtr state)
{
    // Convert the destination buffer to a pointer
    fixed (char* ptr = &MemoryMarshal.GetReference(buffer))
    {
        // Vector only support numeric types, so you cannot use char.
        // char and ushort are both 2 bytes long, so you can convert
        // the pointer from (char*) to (ushort*)
        var input = (ushort*)state.ToPointer();
        var output = (ushort*)ptr;

        var i = (nint)0;
        var n = (nint)(uint)buffer.Length;

        // Use Vector<128> to process 8 characters at a time
        if (Sse41.IsSupported && n >= Vector128<ushort>.Count)
        {
            // Create a Vector128 instance with all elements initialized to '+'
            var vecPlus = Vector128.Create((ushort)'+');

            // Create a Vector128 instance with all elements initialized to '.'
            var vecSpace = Vector128.Create((ushort)' ');

            do
            {
                // Load 8 chars from the input string
                // vec:  ['a', 'a', '+', 'a', '+', 'a', 'a', 'a']
                var vec = Sse2.LoadVector128(input + i);

                // Compare the chars with '+'. The result contains 0x0000 when the char
                // is not equals to '+', and 0xFFFF when it is equals to '+'.
                // The goal is to create a mask which indicate the characters to replace
                // with a space
                //
                // vec:     [ 'a'  ,  'a'  ,  '+'  ,  'a'  ,  '+'  ,  'a'  ,  'a'  ,  'a'  ]
                // vecPlus: [ '+'  ,  '+'  ,  '+'  ,  '+'  ,  '+'  ,  '+'  ,  '+'  ,  '+'  ]
                // mask:    [0x0000, 0x0000, 0xFFFF, 0x0000, 0xFFFF, 0x0000, 0x0000, 0x0000]
                var mask = Sse2.CompareEqual(vec, vecPlus);

                // Replace chars where mask 1 with space
                // vec:      [ 'a'  ,  'a'  ,  '+'  ,  'a'  ,  '+'  ,  'a'  ,  'a'  ,  'a'  ]
                // vecSpace: [ ' '  ,  ' '  ,  ' '  ,  ' '  ,  ' '  ,  ' '  ,  ' '  ,  ' '  ]
                // mask:     [0x0000, 0x0000, 0xFFFF, 0x0000, 0xFFFF, 0x0000, 0x0000, 0x0000]
                // res:      [ 'a'  ,  'a'  ,  ' '  ,  'a'  ,  ' '  ,  'a'  ,  'a'  ,  'a'  ]
                var res = Sse41.BlendVariable(vec, vecSpace, mask);

                // Store the res vector to the output buffer
                Sse2.Store(output + i, res);

                // Process the next 8 chars
                i += Vector128<ushort>.Count;

            } while (i <= n - Vector128<ushort>.Count);
        }

        // Processing the remaining characters (from 0 to 7 chars)
        for (; i < n; ++i)
        {
            if (input[i] != '+')
            {
                output[i] = input[i];
            }
            else
            {
                output[i] = ' ';
            }
        }
    }
}

#Replace specific instructions with Vector128 methods

The previous code uses direct instructions such as Sse41.BlendVariable. This code works, but you need to check if the hardware supports them and you need to provide a fallback implementation when hardware is not supported. .NET provides methods that use SIMD instructions when possible and fall back to a software implementation. This way you don't need to handle the complexity by yourself.

The previous code can be rewritten using the Vector128 static methods:

C#
if (n >= Vector128<ushort>.Count) // No need to check for hardware support
{
    var vecPlus = Vector128.Create((ushort)'+');
    var vecSpace = Vector128.Create((ushort)' ');

    do
    {
        // Equivalent of Sse2.LoadVector128(input + i);
        var vec = Vector128.Load(input + i);

        // Equivalent of Sse2.CompareEqual(vec, vecPlus);
        var mask = Vector128.Equals(vec, vecPlus);

        // Equivalent of Sse41.BlendVariable(vec, vecSpace, mask);
        var res = Vector128.ConditionalSelect(mask, vecSpace, vec);

        // Equivalent of Sse2.Store(output + i, res);
        res.Store(output + i);

        i += Vector128<ushort>.Count;

    } while (i <= n - Vector128<ushort>.Count);
}

for (; i < n; ++i)
{
    if (input[i] != '+')
    {
        output[i] = input[i];
    }
    else
    {
        output[i] = ' ';
    }
}

#Improve the method with AVX2

AVX2 provides instructions to process 16 characters at a time. The code is very similar to the SSE instruction set. Similar to Vector128, you can use Vector256 to avoid handling the complexity of providing a software implementation. Let's see if this improves the performance.

C#
private static unsafe void ReplacePlusWithSpaceCore(Span<char> buffer, IntPtr state)
{
    fixed (char* ptr = &MemoryMarshal.GetReference(buffer))
    {
        var input = (ushort*)state.ToPointer();
        var output = (ushort*)ptr;

        var i = (nint)0;
        var n = (nint)(uint)buffer.Length;

        // Process 16 chars per loop
        if (Vector256.IsHardwareAccelerated && n >= Vector256<ushort>.Count)
        {
            var vecPlus = Vector256.Create((ushort)'+');
            var vecSpace = Vector256.Create((ushort)' ');

            do
            {
                var vec = Vector256.Load(input + i);
                var mask = Vector256.Equals(vec, vecPlus);
                var res = Vector256.ConditionalSelect(mask, vecSpace, vec);
                res.Store(output + i);
                i += Vector256<ushort>.Count;
            } while (i <= n - Vector256<ushort>.Count);
        }

        // Process 8 chars per loop
        if (Vector128.IsHardwareAccelerated && n - i >= Vector128<ushort>.Count)
        {
            var vecPlus = Vector128.Create((ushort)'+');
            var vecSpace = Vector128.Create((ushort)' ');

            do
            {
                var vec = Vector128.Load(input + i);
                var mask = Vector128.Equals(vec, vecPlus);
                var res = Vector128.ConditionalSelect(mask, vecSpace, vec);
                res.Store(output + i);
                i += Vector128<ushort>.Count;
            } while (i <= n - Vector128<ushort>.Count);
        }

        // Processing the remaining characters (from 0 to 7 chars)
        for (; i < n; ++i)
        {
            if (input[i] != '+')
            {
                output[i] = input[i];
            }
            else
            {
                output[i] = ' ';
            }
        }
    }
}

#Benchmark

Let's compare all the previous implementations, plus string.Replace. While string.Replace cannot be used in this case as the source is a ReadOnlySpan<char> and not a string, it gives a good indication of the performance of this method.

Benchmark code
C#
[ReturnValueValidator]
public class ReplacePlusWithSpaceBenchmark
{
    [ParamsSource(nameof(ValueSource))]
    public string Value { get; set; } = null!;

    public IEnumerable<string> ValueSource
    {
        get
        {
            for (int i = 0; i < 128; i += 6)
            {
                yield return string.Create(i, state: (object?)null, (span, state) =>
                {
                    for (var i = 0; i < span.Length; i++)
                    {
                        span[i] = i % 5 == 0 ? '+' : 'a';
                    }
                });
            }
        }
    }

    [Benchmark()]
    public string Basic() => BasicHelper.ReplacePlusWithSpace(Value);

    [Benchmark]
    public string StringReplace() => Value.Replace('+', ' ');

    [Benchmark(Baseline = true)]
    public string Current() => Vector128Helper_Sse.ReplacePlusWithSpace(Value);

    [Benchmark]
    public string Vector128() => Vector128Helper.ReplacePlusWithSpace(Value);

    [Benchmark]
    public string Vector256() => Vector256Helper.ReplacePlusWithSpace(Value);
}

public static class BasicHelper
{
    private static readonly SpanAction<char, IntPtr> s_replacePlusWithSpace = ReplacePlusWithSpaceCore;

    [MethodImpl(MethodImplOptions.AggressiveInlining)]
    public static unsafe string ReplacePlusWithSpace(ReadOnlySpan<char> span)
    {
        fixed (char* ptr = &MemoryMarshal.GetReference(span))
        {
            return string.Create(span.Length, (IntPtr)ptr, s_replacePlusWithSpace);
        }
    }

    private static unsafe void ReplacePlusWithSpaceCore(Span<char> buffer, IntPtr state)
    {
        fixed (char* ptr = &MemoryMarshal.GetReference(buffer))
        {
            var input = (ushort*)state.ToPointer();
            var output = (ushort*)ptr;
            var i = (nint)0;
            var n = (nint)(uint)buffer.Length;
            for (; i < n; ++i)
            {
                if (input[i] != '+')
                {
                    output[i] = input[i];
                }
                else
                {
                    output[i] = ' ';
                }
            }
        }
    }
}

public static class Vector128Helper_Sse
{
    private static readonly SpanAction<char, IntPtr> s_replacePlusWithSpace = ReplacePlusWithSpaceCore;

    [MethodImpl(MethodImplOptions.AggressiveInlining)]
    public static unsafe string ReplacePlusWithSpace(ReadOnlySpan<char> span)
    {
        fixed (char* ptr = &MemoryMarshal.GetReference(span))
        {
            return string.Create(span.Length, (IntPtr)ptr, s_replacePlusWithSpace);
        }
    }

    private static unsafe void ReplacePlusWithSpaceCore(Span<char> buffer, IntPtr state)
    {
        fixed (char* ptr = &MemoryMarshal.GetReference(buffer))
        {
            var input = (ushort*)state.ToPointer();
            var output = (ushort*)ptr;

            var i = (nint)0;
            var n = (nint)(uint)buffer.Length;

            if (Sse41.IsSupported && n >= Vector128<ushort>.Count)
            {
                var vecPlus = Vector128.Create((ushort)'+');
                var vecSpace = Vector128.Create((ushort)' ');

                do
                {
                    var vec = Sse2.LoadVector128(input + i);
                    var mask = Sse2.CompareEqual(vec, vecPlus);
                    var res = Sse41.BlendVariable(vec, vecSpace, mask);
                    Sse2.Store(output + i, res);
                    i += Vector128<ushort>.Count;

                } while (i <= n - Vector128<ushort>.Count);
            }

            for (; i < n; ++i)
            {
                if (input[i] != '+')
                {
                    output[i] = input[i];
                }
                else
                {
                    output[i] = ' ';
                }
            }
        }
    }
}

public static class Vector128Helper
{
    private static readonly SpanAction<char, IntPtr> s_replacePlusWithSpace = ReplacePlusWithSpaceCore;

    [MethodImpl(MethodImplOptions.AggressiveInlining)]
    public static unsafe string ReplacePlusWithSpace(ReadOnlySpan<char> span)
    {
        fixed (char* ptr = &MemoryMarshal.GetReference(span))
        {
            return string.Create(span.Length, (IntPtr)ptr, s_replacePlusWithSpace);
        }
    }

    public static unsafe void ReplacePlusWithSpaceCore(Span<char> buffer, IntPtr state)
    {
        fixed (char* ptr = &MemoryMarshal.GetReference(buffer))
        {
            var input = (ushort*)state.ToPointer();
            var output = (ushort*)ptr;
            var i = (nint)0;
            var n = (nint)(uint)buffer.Length;

            if (n >= Vector128<ushort>.Count)
            {
                var vecPlus = Vector128.Create((ushort)'+');
                var vecSpace = Vector128.Create((ushort)' ');

                do
                {
                    var vec = Vector128.Load(input + i);
                    var mask = Vector128.Equals(vec, vecPlus);
                    var res = Vector128.ConditionalSelect(mask, vecSpace, vec);
                    res.Store(output + i);
                    i += Vector128<ushort>.Count;

                } while (i <= n - Vector128<ushort>.Count);
            }

            for (; i < n; ++i)
            {
                if (input[i] != '+')
                {
                    output[i] = input[i];
                }
                else
                {
                    output[i] = ' ';
                }
            }
        }
    }
}

public static class Vector256Helper
{
    private static readonly SpanAction<char, IntPtr> s_replacePlusWithSpace = ReplacePlusWithSpaceCore;

    [MethodImpl(MethodImplOptions.AggressiveInlining)]
    public static unsafe string ReplacePlusWithSpace(ReadOnlySpan<char> span)
    {
        fixed (char* ptr = &MemoryMarshal.GetReference(span))
        {
            return string.Create(span.Length, (IntPtr)ptr, s_replacePlusWithSpace);
        }
    }

    private static unsafe void ReplacePlusWithSpaceCore(Span<char> buffer, IntPtr state)
    {
        fixed (char* ptr = &MemoryMarshal.GetReference(buffer))
        {
            var input = (ushort*)state.ToPointer();
            var output = (ushort*)ptr;

            var i = (nint)0;
            var n = (nint)(uint)buffer.Length;

            if (Vector256.IsHardwareAccelerated && n >= Vector256<ushort>.Count)
            {
                var vecPlus = Vector256.Create((ushort)'+');
                var vecSpace = Vector256.Create((ushort)' ');

                do
                {
                    var vec = Vector256.Load(input + i);
                    var mask = Vector256.Equals(vec, vecPlus);
                    var res = Vector256.ConditionalSelect(mask, vecSpace, vec);
                    res.Store(output + i);
                    i += Vector256<ushort>.Count;
                } while (i <= n - Vector256<ushort>.Count);
            }

            if (Vector128.IsHardwareAccelerated && n - i >= Vector128<ushort>.Count)
            {
                var vecPlus = Vector128.Create((ushort)'+');
                var vecSpace = Vector128.Create((ushort)' ');

                do
                {
                    var vec = Vector128.Load(input + i);
                    var mask = Vector128.Equals(vec, vecPlus);
                    var res = Vector128.ConditionalSelect(mask, vecSpace, vec);
                    res.Store(output + i);
                    i += Vector128<ushort>.Count;
                } while (i <= n - Vector128<ushort>.Count);
            }

            for (; i < n; ++i)
            {
                if (input[i] != '+')
                {
                    output[i] = input[i];
                }
                else
                {
                    output[i] = ' ';
                }
            }
        }
    }
}
INI
BenchmarkDotNet=v0.13.1, OS=Windows 10.0.22621
AMD Ryzen 7 5800X, 1 CPU, 16 logical and 8 physical cores
.NET SDK=7.0.100-preview.5.22307.18
  [Host]    : .NET 7.0.0 (7.0.22.30112), X64 RyuJIT
  RyuJitX64 : .NET 7.0.0 (7.0.22.31201), X64 RyuJIT

Job=RyuJitX64  Jit=RyuJit  Platform=X64
Toolchain=.NET 7.0.100-preview.6.22316.8
MethodValueMeanErrorStdDevMedianRatioRatioSD
NoVectorlength: 01.356 ns0.0242 ns0.0215 ns1.360 ns0.970.04
StringReplace2.303 ns0.0543 ns0.0508 ns2.292 ns1.650.07
Vector128_SSE1.395 ns0.0563 ns0.0526 ns1.398 ns1.000.00
Vector1281.347 ns0.0294 ns0.0275 ns1.346 ns0.970.04
Vector2561.344 ns0.0353 ns0.0330 ns1.338 ns0.960.04
NoVectorlength: 68.114 ns0.1864 ns0.1744 ns8.094 ns0.920.02
StringReplace7.713 ns0.1334 ns0.1114 ns7.726 ns0.870.01
Vector128_SSE8.849 ns0.1527 ns0.1354 ns8.826 ns1.000.00
Vector1288.122 ns0.1614 ns0.1510 ns8.132 ns0.920.02
Vector2568.476 ns0.1463 ns0.1797 ns8.463 ns0.960.02
NoVectorlength: 1210.453 ns0.1788 ns0.1673 ns10.450 ns1.430.03
StringReplace9.892 ns0.2260 ns0.3837 ns9.723 ns1.370.07
Vector128_SSE7.319 ns0.1260 ns0.1052 ns7.349 ns1.000.00
Vector1287.219 ns0.1584 ns0.1404 ns7.185 ns0.990.03
Vector2567.931 ns0.2017 ns0.3141 ns7.896 ns1.080.06
NoVectorlength: 1814.643 ns0.3154 ns0.2950 ns14.686 ns2.050.04
StringReplace7.482 ns0.1681 ns0.1573 ns7.546 ns1.050.03
Vector128_SSE7.147 ns0.1176 ns0.1043 ns7.127 ns1.000.00
Vector1287.218 ns0.1173 ns0.1040 ns7.215 ns1.010.02
Vector2567.334 ns0.1345 ns0.1192 ns7.357 ns1.030.02
NoVectorlength: 2417.528 ns0.3382 ns0.3164 ns17.579 ns2.470.04
StringReplace10.534 ns0.2072 ns0.1938 ns10.548 ns1.490.03
Vector128_SSE7.095 ns0.1372 ns0.1216 ns7.112 ns1.000.00
Vector1287.110 ns0.1326 ns0.1176 ns7.117 ns1.000.02
Vector2567.111 ns0.1426 ns0.1264 ns7.079 ns1.000.03
NoVectorlength: 3020.800 ns0.3545 ns0.3316 ns20.804 ns2.100.05
StringReplace13.720 ns0.2893 ns0.2564 ns13.766 ns1.380.03
Vector128_SSE9.928 ns0.1773 ns0.1659 ns9.936 ns1.000.00
Vector12810.372 ns0.2506 ns0.5175 ns10.269 ns1.040.05
Vector25610.364 ns0.2515 ns0.4536 ns10.261 ns1.060.05
NoVectorlength: 3623.607 ns0.5093 ns0.5002 ns23.813 ns2.510.06
StringReplace9.577 ns0.1544 ns0.1369 ns9.622 ns1.010.02
Vector128_SSE9.428 ns0.2271 ns0.2125 ns9.533 ns1.000.00
Vector1289.192 ns0.1516 ns0.1344 ns9.246 ns0.970.02
Vector2569.470 ns0.1724 ns0.1613 ns9.471 ns1.000.03
NoVectorlength: 4226.764 ns0.4799 ns0.4489 ns26.736 ns2.940.08
StringReplace+aaaa(…)aaa+a [42]12.787 ns0.2885 ns0.2699 ns12.825 ns1.400.04
Vector128_SSE+aaaa(…)aaa+a [42]9.119 ns0.2222 ns0.2079 ns9.062 ns1.000.00
Vector128+aaaa(…)aaa+a [42]8.866 ns0.2225 ns0.2185 ns8.886 ns0.970.03
Vector256+aaaa(…)aaa+a [42]9.017 ns0.1865 ns0.1653 ns9.013 ns0.990.02
NoVectorlength: 4829.749 ns0.6195 ns0.6629 ns29.779 ns3.290.06
StringReplace+aaaa(…)aa+aa [48]9.144 ns0.2211 ns0.2716 ns9.142 ns1.000.03
Vector128_SSE+aaaa(…)aa+aa [48]9.017 ns0.1193 ns0.1116 ns9.034 ns1.000.00
Vector128+aaaa(…)aa+aa [48]9.137 ns0.2230 ns0.2655 ns9.173 ns1.020.03
Vector256+aaaa(…)aa+aa [48]8.659 ns0.1974 ns0.1846 ns8.644 ns0.960.02
NoVectorlength: 5432.712 ns0.7051 ns0.7240 ns32.727 ns2.840.10
StringReplace+aaaa(…)a+aaa [54]11.934 ns0.2786 ns0.2736 ns11.978 ns1.040.03
Vector128_SSE+aaaa(…)a+aaa [54]11.478 ns0.2334 ns0.3347 ns11.429 ns1.000.00
Vector128+aaaa(…)a+aaa [54]11.415 ns0.2562 ns0.2517 ns11.439 ns0.990.04
Vector256+aaaa(…)a+aaa [54]11.469 ns0.1760 ns0.1470 ns11.463 ns1.000.02
NoVectorlength: 6036.040 ns0.2408 ns0.2134 ns36.058 ns3.240.09
StringReplace+aaaa(…)+aaaa [60]15.078 ns0.3391 ns0.3769 ns15.224 ns1.360.05
Vector128_SSE+aaaa(…)+aaaa [60]11.122 ns0.2348 ns0.2610 ns11.033 ns1.000.00
Vector128+aaaa(…)+aaaa [60]11.103 ns0.1660 ns0.1553 ns11.142 ns1.000.03
Vector256+aaaa(…)+aaaa [60]11.104 ns0.2501 ns0.2456 ns11.110 ns1.000.03
NoVectorlength: 6644.250 ns0.9163 ns1.4266 ns44.660 ns4.010.17
StringReplace+aaaa(…)aaaa+ [66]11.166 ns0.2671 ns0.4462 ns11.161 ns1.030.04
Vector128_SSE+aaaa(…)aaaa+ [66]11.017 ns0.2536 ns0.2920 ns10.975 ns1.000.00
Vector128+aaaa(…)aaaa+ [66]10.870 ns0.2487 ns0.2443 ns10.945 ns0.980.04
Vector256+aaaa(…)aaaa+ [66]10.577 ns0.2520 ns0.2902 ns10.585 ns0.960.04
NoVectorlength: 7249.050 ns1.0280 ns2.4630 ns48.317 ns4.440.31
StringReplace+aaaa(…)aaa+a [72]14.954 ns0.3354 ns0.3993 ns14.937 ns1.300.04
Vector128_SSE+aaaa(…)aaa+a [72]11.471 ns0.2678 ns0.2976 ns11.399 ns1.000.00
Vector128+aaaa(…)aaa+a [72]10.723 ns0.2582 ns0.6185 ns10.477 ns0.980.04
Vector256+aaaa(…)aaa+a [72]9.981 ns0.1095 ns0.0971 ns9.976 ns0.870.03
NoVectorlength: 7847.917 ns0.3747 ns0.3322 ns47.879 ns3.740.07
StringReplace+aaaa(…)aa+aa [78]17.697 ns0.2877 ns0.2402 ns17.765 ns1.380.03
Vector128_SSE+aaaa(…)aa+aa [78]12.785 ns0.2556 ns0.2391 ns12.803 ns1.000.00
Vector128+aaaa(…)aa+aa [78]12.841 ns0.2229 ns0.1976 ns12.776 ns1.000.03
Vector256+aaaa(…)aa+aa [78]12.558 ns0.0856 ns0.0715 ns12.577 ns0.980.02
NoVectorlength: 8450.057 ns0.5688 ns0.5042 ns50.061 ns3.930.08
StringReplace+aaaa(…)a+aaa [84]13.632 ns0.3164 ns0.6533 ns13.516 ns1.090.06
Vector128_SSE+aaaa(…)a+aaa [84]12.739 ns0.2543 ns0.2379 ns12.680 ns1.000.00
Vector128+aaaa(…)a+aaa [84]13.454 ns0.3139 ns0.6049 ns13.321 ns1.060.06
Vector256+aaaa(…)a+aaa [84]11.861 ns0.2198 ns0.2056 ns11.848 ns0.930.03
NoVectorlength: 9054.707 ns1.1174 ns0.9906 ns54.314 ns4.470.10
StringReplace+aaaa(…)+aaaa [90]16.048 ns0.2490 ns0.2329 ns16.068 ns1.310.02
Vector128_SSE+aaaa(…)+aaaa [90]12.218 ns0.2353 ns0.2201 ns12.077 ns1.000.00
Vector128+aaaa(…)+aaaa [90]12.531 ns0.1651 ns0.1289 ns12.543 ns1.020.02
Vector256+aaaa(…)+aaaa [90]12.388 ns0.2572 ns0.4153 ns12.268 ns1.020.03
NoVectorlength: 9657.387 ns1.1987 ns1.4270 ns57.483 ns4.640.12
StringReplace+aaaa(…)aaaa+ [96]12.177 ns0.1435 ns0.1272 ns12.139 ns0.990.02
Vector128_SSE+aaaa(…)aaaa+ [96]12.346 ns0.1922 ns0.1798 ns12.315 ns1.000.00
Vector128+aaaa(…)aaaa+ [96]12.476 ns0.2137 ns0.1999 ns12.439 ns1.010.02
Vector256+aaaa(…)aaaa+ [96]11.463 ns0.2065 ns0.1932 ns11.414 ns0.930.02
NoVectorlength: 10261.076 ns1.0319 ns0.9148 ns60.989 ns3.090.07
StringReplace+aaa(…)aa+a [102]15.050 ns0.1900 ns0.1684 ns15.052 ns0.760.01
Vector128_SSE+aaa(…)aa+a [102]19.770 ns0.4355 ns0.3861 ns19.693 ns1.000.00
Vector128+aaa(…)aa+a [102]14.953 ns0.2883 ns0.2697 ns15.006 ns0.760.02
Vector256+aaa(…)aa+a [102]13.722 ns0.2105 ns0.1969 ns13.753 ns0.690.02
NoVectorlength: 10863.958 ns1.2594 ns1.3476 ns63.442 ns4.540.13
StringReplace+aaa(…)a+aa [108]18.385 ns0.3784 ns0.3539 ns18.310 ns1.300.03
Vector128_SSE+aaa(…)a+aa [108]14.150 ns0.2219 ns0.1967 ns14.085 ns1.000.00
Vector128+aaa(…)a+aa [108]14.252 ns0.2817 ns0.2635 ns14.239 ns1.010.02
Vector256+aaa(…)a+aa [108]14.180 ns0.3240 ns0.3858 ns14.108 ns1.010.03
NoVectorlength: 11468.325 ns1.3614 ns1.2735 ns68.216 ns4.820.11
StringReplace+aaa(…)+aaa [114]16.224 ns0.3027 ns0.2832 ns16.098 ns1.150.02
Vector128_SSE+aaa(…)+aaa [114]14.169 ns0.2171 ns0.2031 ns14.163 ns1.000.00
Vector128+aaa(…)+aaa [114]13.950 ns0.3075 ns0.2726 ns13.925 ns0.990.02
Vector256+aaa(…)+aaa [114]13.143 ns0.2048 ns0.1915 ns13.131 ns0.930.02
NoVectorlength: 12069.259 ns1.1361 ns1.0627 ns69.383 ns5.120.07
StringReplace+aaa(…)aaaa [120]16.914 ns0.3347 ns0.3131 ns16.787 ns1.250.02
Vector128_SSE+aaa(…)aaaa [120]13.560 ns0.1446 ns0.1208 ns13.584 ns1.000.00
Vector128+aaa(…)aaaa [120]15.498 ns0.5173 ns1.4247 ns14.943 ns1.260.13
Vector256+aaa(…)aaaa [120]13.000 ns0.2672 ns0.2369 ns13.008 ns0.960.02
NoVectorlength: 12672.409 ns1.3006 ns1.1530 ns72.176 ns4.460.08
StringReplace20.305 ns0.2024 ns0.1690 ns20.280 ns1.250.01
Vector128_SSE16.229 ns0.0853 ns0.0712 ns16.219 ns1.000.00
Vector12818.434 ns0.4324 ns1.2613 ns18.124 ns1.170.11
Vector25617.148 ns0.3881 ns0.7478 ns17.190 ns1.040.03

#Additional resources

Do you have a question or a suggestion about this post? Contact me!

Follow me:
Enjoy this blog?Buy Me A Coffee💖 Sponsor on GitHub