Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Remove pinning from StringBuilder #64405

Merged
merged 1 commit into from
Jan 28, 2022
Merged

Conversation

stephentoub
Copy link
Member

Switch from pointers to refs to avoid pinning the inputs.

As part of this, I also consolidated the fast-path that was there specifically for string inputs to also apply to span inputs, char arrays, pointers, etc.

Method Toolchain Value Mean Ratio Code Size
String \main\corerun.exe a 2.958 ns 1.00 719 B
String \pr\corerun.exe a 2.127 ns 0.72 684 B
Span \main\corerun.exe a 7.652 ns 1.00 1,170 B
Span \pr\corerun.exe a 2.516 ns 0.33 693 B
CharArray \main\corerun.exe a 6.631 ns 1.00 1,171 B
CharArray \pr\corerun.exe a 2.133 ns 0.32 684 B
AppendJoin \main\corerun.exe a 33.384 ns 1.00 821 B
AppendJoin \pr\corerun.exe a 22.326 ns 0.67 811 B
String \main\corerun.exe ab 3.432 ns 1.00 719 B
String \pr\corerun.exe ab 2.391 ns 0.70 684 B
Span \main\corerun.exe ab 7.274 ns 1.00 1,170 B
Span \pr\corerun.exe ab 2.932 ns 0.40 693 B
CharArray \main\corerun.exe ab 6.384 ns 1.00 1,171 B
CharArray \pr\corerun.exe ab 2.374 ns 0.37 684 B
AppendJoin \main\corerun.exe ab 36.239 ns 1.00 821 B
AppendJoin \pr\corerun.exe ab 22.674 ns 0.63 811 B
String \main\corerun.exe abc 4.760 ns 1.00 719 B
String \pr\corerun.exe abc 4.348 ns 0.91 684 B
Span \main\corerun.exe abc 7.354 ns 1.00 1,170 B
Span \pr\corerun.exe abc 4.811 ns 0.66 693 B
CharArray \main\corerun.exe abc 6.372 ns 1.00 1,171 B
CharArray \pr\corerun.exe abc 4.330 ns 0.68 684 B
AppendJoin \main\corerun.exe abc 38.682 ns 1.00 821 B
AppendJoin \pr\corerun.exe abc 31.658 ns 0.82 811 B
String \main\corerun.exe abcd 4.699 ns 1.00 719 B
String \pr\corerun.exe abcd 4.599 ns 0.98 684 B
Span \main\corerun.exe abcd 7.296 ns 1.00 1,170 B
Span \pr\corerun.exe abcd 4.950 ns 0.68 693 B
CharArray \main\corerun.exe abcd 6.390 ns 1.00 1,171 B
CharArray \pr\corerun.exe abcd 4.469 ns 0.70 684 B
AppendJoin \main\corerun.exe abcd 39.048 ns 1.00 821 B
AppendJoin \pr\corerun.exe abcd 30.638 ns 0.78 811 B
String \main\corerun.exe abcdefgh 4.698 ns 1.00 719 B
String \pr\corerun.exe abcdefgh 4.504 ns 0.96 684 B
Span \main\corerun.exe abcdefgh 7.366 ns 1.00 1,170 B
Span \pr\corerun.exe abcdefgh 5.057 ns 0.69 693 B
CharArray \main\corerun.exe abcdefgh 6.353 ns 1.00 1,171 B
CharArray \pr\corerun.exe abcdefgh 4.546 ns 0.72 684 B
AppendJoin \main\corerun.exe abcdefgh 39.165 ns 1.00 821 B
AppendJoin \pr\corerun.exe abcdefgh 30.765 ns 0.79 811 B
using System;
using System.Linq;
using System.Runtime.CompilerServices;
using System.Text;
using System.Text.RegularExpressions;
using BenchmarkDotNet.Attributes;
using BenchmarkDotNet.Running;

[DisassemblyDiagnoser]
public class Program
{
    public static void Main(string[] args) => BenchmarkSwitcher.FromAssembly(typeof(Program).Assembly).Run(args);

    private const int Iterations = 1000;

    private StringBuilder _sb = new StringBuilder(100_000);
    private char[] _chars;
    private string[] _strings;

    [Params("a", "ab", "abc", "abcd", "abcdefgh")]
    public string Value { get; set; }

    [GlobalSetup]
    public void Setup()
    {
        _chars = Value.ToCharArray();
        _strings = Enumerable.Repeat(Value, 4).ToArray();
    }

    [Benchmark(OperationsPerInvoke = Iterations)]
    public void String()
    {
        _sb.Clear();
        for (int i = 0; i < Iterations; i++) _sb.Append(Value);
    }

    [Benchmark(OperationsPerInvoke = Iterations)]
    public void Span()
    {
        _sb.Clear();
        for (int i = 0; i < Iterations; i++) _sb.Append((ReadOnlySpan<char>)Value);
    }

    [Benchmark(OperationsPerInvoke = Iterations)]
    public void CharArray()
    {
        _sb.Clear();
        for (int i = 0; i < Iterations; i++) _sb.Append(_chars);
    }

    [Benchmark(OperationsPerInvoke = Iterations)]
    public void AppendJoin()
    {
        _sb.Clear();
        for (int i = 0; i < Iterations; i++)
        {
            _sb.AppendJoin(", ", _strings);
        }
    }
}

@dotnet-issue-labeler
Copy link

I couldn't figure out the best area label to add to this PR. If you have write-permissions please help me learn by adding exactly one area label.

@ghost ghost assigned stephentoub Jan 27, 2022
@stephentoub stephentoub added this to the 7.0.0 milestone Jan 27, 2022
@ghost
Copy link

ghost commented Jan 27, 2022

Tagging subscribers to this area: @dotnet/area-system-runtime
See info in area-owners.md if you want to be subscribed.

Issue Details

Switch from pointers to refs to avoid pinning the inputs.

As part of this, I also consolidated the fast-path that was there specifically for string inputs to also apply to span inputs, char arrays, pointers, etc.

Method Toolchain Value Mean Ratio Code Size
String \main\corerun.exe a 2.958 ns 1.00 719 B
String \pr\corerun.exe a 2.127 ns 0.72 684 B
Span \main\corerun.exe a 7.652 ns 1.00 1,170 B
Span \pr\corerun.exe a 2.516 ns 0.33 693 B
CharArray \main\corerun.exe a 6.631 ns 1.00 1,171 B
CharArray \pr\corerun.exe a 2.133 ns 0.32 684 B
AppendJoin \main\corerun.exe a 33.384 ns 1.00 821 B
AppendJoin \pr\corerun.exe a 22.326 ns 0.67 811 B
String \main\corerun.exe ab 3.432 ns 1.00 719 B
String \pr\corerun.exe ab 2.391 ns 0.70 684 B
Span \main\corerun.exe ab 7.274 ns 1.00 1,170 B
Span \pr\corerun.exe ab 2.932 ns 0.40 693 B
CharArray \main\corerun.exe ab 6.384 ns 1.00 1,171 B
CharArray \pr\corerun.exe ab 2.374 ns 0.37 684 B
AppendJoin \main\corerun.exe ab 36.239 ns 1.00 821 B
AppendJoin \pr\corerun.exe ab 22.674 ns 0.63 811 B
String \main\corerun.exe abc 4.760 ns 1.00 719 B
String \pr\corerun.exe abc 4.348 ns 0.91 684 B
Span \main\corerun.exe abc 7.354 ns 1.00 1,170 B
Span \pr\corerun.exe abc 4.811 ns 0.66 693 B
CharArray \main\corerun.exe abc 6.372 ns 1.00 1,171 B
CharArray \pr\corerun.exe abc 4.330 ns 0.68 684 B
AppendJoin \main\corerun.exe abc 38.682 ns 1.00 821 B
AppendJoin \pr\corerun.exe abc 31.658 ns 0.82 811 B
String \main\corerun.exe abcd 4.699 ns 1.00 719 B
String \pr\corerun.exe abcd 4.599 ns 0.98 684 B
Span \main\corerun.exe abcd 7.296 ns 1.00 1,170 B
Span \pr\corerun.exe abcd 4.950 ns 0.68 693 B
CharArray \main\corerun.exe abcd 6.390 ns 1.00 1,171 B
CharArray \pr\corerun.exe abcd 4.469 ns 0.70 684 B
AppendJoin \main\corerun.exe abcd 39.048 ns 1.00 821 B
AppendJoin \pr\corerun.exe abcd 30.638 ns 0.78 811 B
String \main\corerun.exe abcdefgh 4.698 ns 1.00 719 B
String \pr\corerun.exe abcdefgh 4.504 ns 0.96 684 B
Span \main\corerun.exe abcdefgh 7.366 ns 1.00 1,170 B
Span \pr\corerun.exe abcdefgh 5.057 ns 0.69 693 B
CharArray \main\corerun.exe abcdefgh 6.353 ns 1.00 1,171 B
CharArray \pr\corerun.exe abcdefgh 4.546 ns 0.72 684 B
AppendJoin \main\corerun.exe abcdefgh 39.165 ns 1.00 821 B
AppendJoin \pr\corerun.exe abcdefgh 30.765 ns 0.79 811 B
using System;
using System.Linq;
using System.Runtime.CompilerServices;
using System.Text;
using System.Text.RegularExpressions;
using BenchmarkDotNet.Attributes;
using BenchmarkDotNet.Running;

[DisassemblyDiagnoser]
public class Program
{
    public static void Main(string[] args) => BenchmarkSwitcher.FromAssembly(typeof(Program).Assembly).Run(args);

    private const int Iterations = 1000;

    private StringBuilder _sb = new StringBuilder(100_000);
    private char[] _chars;
    private string[] _strings;

    [Params("a", "ab", "abc", "abcd", "abcdefgh")]
    public string Value { get; set; }

    [GlobalSetup]
    public void Setup()
    {
        _chars = Value.ToCharArray();
        _strings = Enumerable.Repeat(Value, 4).ToArray();
    }

    [Benchmark(OperationsPerInvoke = Iterations)]
    public void String()
    {
        _sb.Clear();
        for (int i = 0; i < Iterations; i++) _sb.Append(Value);
    }

    [Benchmark(OperationsPerInvoke = Iterations)]
    public void Span()
    {
        _sb.Clear();
        for (int i = 0; i < Iterations; i++) _sb.Append((ReadOnlySpan<char>)Value);
    }

    [Benchmark(OperationsPerInvoke = Iterations)]
    public void CharArray()
    {
        _sb.Clear();
        for (int i = 0; i < Iterations; i++) _sb.Append(_chars);
    }

    [Benchmark(OperationsPerInvoke = Iterations)]
    public void AppendJoin()
    {
        _sb.Clear();
        for (int i = 0; i < Iterations; i++)
        {
            _sb.AppendJoin(", ", _strings);
        }
    }
}
Author: stephentoub
Assignees: stephentoub
Labels:

area-System.Runtime, tenet-performance

Milestone: 7.0.0

Switch from pointers to refs to avoid pinning the inputs.

As part of this, I also consolidated the fast-path that was there specifically for string inputs to also apply to span inputs, char arrays, pointers, etc.
@stephentoub stephentoub merged commit 315fb41 into dotnet:main Jan 28, 2022
@stephentoub stephentoub deleted the sbpin branch January 28, 2022 20:04
@EgorBo
Copy link
Member

EgorBo commented Feb 1, 2022

Improvements on Win-x64: dotnet/perf-autofiling-issues#3219

@stephentoub
Copy link
Member Author

Improvements on Win-x64: dotnet/perf-autofiling-issues#3219

Nice, thanks.

@EgorBo
Copy link
Member

EgorBo commented Feb 1, 2022

Improvement for DateTimeOffset.ToString if it's related: dotnet/perf-autofiling-issues#3239

@stephentoub
Copy link
Member Author

stephentoub commented Feb 1, 2022

if it's related

Likely. That test uses ToString(null) which will end up formatting into a StringBuilder.

Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants