-
Notifications
You must be signed in to change notification settings - Fork 5.3k
Improve string.Format / {Value}StringBuilder.AppendFormat parsing throughput #69757
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
Conversation
…oughput The primary purpose of this change was to use IndexOfAny as part of parsing a composite string format in order to look for the next brace in the string: for longer composite formats with fewer holes, this can significantly speed up the parsing. However, for very small strings filled with formats, the overhead of the IndexOfAny is unnecessary, and so I ended up overhauling the implementation to gain back the losses for those shorter cases, e.g. by avoiding bounds checking, by favoring expected cases, etc., and also generally cleaned up the implementation. As part of this, I also deleted the internal ParamsArray helper struct and replaced it with use of `ReadOnlySpan<object?>`.
|
Tagging subscribers to this area: @dotnet/area-system-runtime Issue DetailsThe primary purpose of this change was to use IndexOfAny as part of parsing a composite string format in order to look for the next brace in the string: for longer composite formats with fewer holes, this can significantly speed up the parsing. However, for very small strings filled with formats, the overhead of the IndexOfAny is unnecessary and resulted in regressions in some benchmarks, so I ended up overhauling the implementation to gain back the losses for those shorter cases, e.g. by avoiding bounds checking, by favoring expected cases, etc., and also generally cleaned up the implementation. As part of this, I also deleted the internal ParamsArray helper struct and replaced it with use of
using BenchmarkDotNet.Attributes;
using BenchmarkDotNet.Running;
using System.Runtime.CompilerServices;
using System.Text;
public class Program
{
static void Main(string[] args) => BenchmarkSwitcher.FromAssembly(typeof(Program).Assembly).Run(args);
private StringBuilder _sb = new StringBuilder();
private object _int = 12345;
[Benchmark]
public void AppendFormat_Small_Strings()
{
_sb.Clear();
_sb.AppendFormat("{0}: {1}", "key", "value");
}
[Benchmark]
public void AppendFormat_Medium_Strings()
{
_sb.Clear();
_sb.AppendFormat("Key: {0}, Value: {1}", "key", "value");
}
[Benchmark]
public void AppendFormat_Large_Strings()
{
_sb.Clear();
_sb.AppendFormat("This is a test to see what happens when there's more text {0} the individual {1} that need to be {2} in.", "between", "arguments", "filled");
}
[Benchmark]
public void AppendFormat_Varying()
{
_sb.Clear();
_sb.AppendFormat("{0:X}.{1,10:X4}.{2,-4}.{3:d10}", _int, _int, _int, _int);
}
[Benchmark]
public string Format_Small_Strings()
{
return string.Format("{0}: {1}", "key", "value");
}
[Benchmark]
public string Format_Medium_Strings()
{
return string.Format("Key: {0}, Value: {1}", "key", "value");
}
[Benchmark]
public string Format_Large_Strings()
{
return string.Format("This is a test to see what happens when there's more text {0} the individual {1} that need to be {2} in.", "between", "arguments", "filled");
}
[Benchmark]
public string Format_Varying()
{
return string.Format("{0:X}.{1,10:X4}.{2,-4}.{3:d10}", _int, _int, _int, _int);
}
}
|
src/libraries/System.Private.CoreLib/src/System/Text/ValueStringBuilder.AppendFormat.cs
Show resolved
Hide resolved
| public StringBuilder AppendFormat([StringSyntax(StringSyntaxAttribute.CompositeFormat)] string format, object? arg0, object? arg1, object? arg2) | ||
| { | ||
| ThreeObjects three = new ThreeObjects(arg0, arg1, arg2); | ||
| return AppendFormatHelper(null, format, MemoryMarshal.CreateReadOnlySpan(ref three.Arg0, 3)); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It would be great to implement something like:
MemoryMarshal.CreateReadOnlySpan(ref arg2, 3)Unreal?
The primary purpose of this change was to use IndexOfAny as part of parsing a composite string format in order to look for the next brace in the string: for longer composite formats with fewer holes, this can significantly speed up the parsing.
However, for very small strings filled with formats, the overhead of the IndexOfAny is unnecessary and resulted in regressions in some benchmarks, so I ended up overhauling the implementation to gain back the losses for those shorter cases, e.g. by avoiding bounds checking, by favoring expected cases, etc., and also generally cleaned up the implementation. As part of this, I also deleted the internal ParamsArray helper struct and replaced it with use of
ReadOnlySpan<object?>.