Closed
Description
using System;
using System.Text;
using BenchmarkDotNet.Attributes;
using BenchmarkDotNet.Configs;
using BenchmarkDotNet.Diagnosers;
using BenchmarkDotNet.Running;
namespace MyBenchmarks
{
public class StringBuilderROM
{
private readonly ReadOnlyMemory<char> m1 = "xAbCdEfG".AsMemory().Slice(1);
private readonly ReadOnlyMemory<char> m2 = "xAbCdEfGabcdefghijklmnopqrstuvwxyz".AsMemory().Slice(1);
private readonly StringBuilder s = new StringBuilder();
[Benchmark]
public StringBuilder ShortAppendROM() => s.Clear().Append(m1);
[Benchmark]
public StringBuilder ShortAppendROS() => s.Clear().Append(m1.Span);
[Benchmark]
public StringBuilder LongAppendROM() => s.Clear().Append(m2);
[Benchmark]
public StringBuilder LongAppendROS() => s.Clear().Append(m2.Span);
}
public class Program
{
public static void Main(string[] args)
{
var summary = BenchmarkRunner.Run<StringBuilderROM>(DefaultConfig.Instance.With(MemoryDiagnoser.Default));
}
}
}
Results in:
BenchmarkDotNet=v0.11.1, OS=Windows 10.0.17134.286 (1803/April2018Update/Redstone4)
Intel Core i7-6700 CPU 3.40GHz (Skylake), 1 CPU, 8 logical and 4 physical cores
.NET Core SDK=3.0.100-alpha1-20180720-2
[Host] : .NET Core 2.1.3 (CoreCLR 4.6.26725.06, CoreFX 4.6.26725.05), 64bit RyuJIT
DefaultJob : .NET Core 2.1.3 (CoreCLR 4.6.26725.06, CoreFX 4.6.26725.05), 64bit RyuJIT
Method | Mean | Error | StdDev | Gen 0 | Allocated |
--------------- |---------:|----------:|----------:|-------:|----------:|
ShortAppendROM | 34.48 ns | 0.7666 ns | 0.9968 ns | 0.0171 | 72 B |
ShortAppendROS | 20.06 ns | 0.2060 ns | 0.1927 ns | - | 0 B |
LongAppendROM | 45.51 ns | 0.8662 ns | 0.8103 ns | 0.0305 | 128 B |
LongAppendROS | 20.78 ns | 0.1742 ns | 0.1629 ns | - | 0 B |
The reason StringBuilder.Append(ROM<char>)
even works is because Append
has an overload that takes an object
, which just turns around and calls .ToString()
on the object. ROM<char>
overrides the ToString, and returns string.Substring
, which is going to make a copy of the string. Thus resulting in worse performance.
I'm sure I'm not the last person who is going to make this mistake assuming that StringBuilder.Append(ROM<char>)
should "just work" as expected.
Proposal
A simple fix for this would be to add another overload to StringBuilder.Append
that takes a ReadOnlyMemory<char>
and just calls the existing Append(ReadOnlySpan<char>)
method, which doesn't need to copy the string before appending.
namespace System.Text
{
public class StringBuilder
{
public StringBuilder Append(ReadOnlyMemory<char> value);
}
}