Improve performance of integer formatting#76726
Merged
stephentoub merged 3 commits intodotnet:mainfrom Oct 11, 2022
Merged
Conversation
1. The DivRem(..., 10) for each digit in the number ends up being the most expensive aspect of formatting. This employs a trick other formatting libraries use, which is to have a table for all the formatted values between 00 and 99, and to then DivRem(..., 100) to cut the number of operations in half, which for longer values is meaningful. 2. Avoids going through the digit counting path when we know at the call site it won't be needed. 3. Employs a branch-free, table-based lookup for CountDigits(ulong) to go with a similar approach added for uint.
|
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. |
|
Tagging subscribers to this area: @dotnet/area-system-runtime Issue DetailsFollow-up to #76519 (comment)
The numbers from thesebenchmarks are almost all wins. The main exception is that the TryFormat, 1 digit, no format specifier case gets a little slower... it's repeatable on my machine, but also sub-ns. private ulong[] _uint32Values = new ulong[]
{
1, 12, 123, 1234, 12345, 123456, 1234567, 12345678, 123456789, 1234567890,
};
private ulong[] _uint64Values = new ulong[]
{
1, 12, 123, 1234, 12345, 123456, 1234567, 12345678, 123456789, 1234567890,
12345678901, 123456789012, 1234567890123, 12345678901234, 123456789012345, 1234567890123456, 12345678901234567, 123456789012345678, 1234567890123456789, 12345678901234567890,
};
private char[] _scratch = new char[32];
[Benchmark]
public int UInt32AllLengthsToString()
{
int sum = 0;
foreach (ulong value in _uint32Values) sum += value.ToString().Length;
return sum;
}
[Benchmark]
public bool UInt32AllLengthsTryFormat()
{
bool success = true;
foreach (ulong value in _uint32Values) success |= value.TryFormat(_scratch, out _);
return success;
}
[Benchmark]
public int UInt64AllLengthsToString()
{
int sum = 0;
foreach (ulong value in _uint64Values) sum += value.ToString().Length;
return sum;
}
[Benchmark]
public bool UInt64AllLengthsTryFormat()
{
bool success = true;
foreach (ulong value in _uint64Values) success |= value.TryFormat(_scratch, out _);
return success;
}
[Benchmark]
[Arguments(1)]
[Arguments(12)]
[Arguments(123)]
[Arguments(1234)]
[Arguments(12345)]
[Arguments(123456)]
[Arguments(1234567)]
[Arguments(12345678)]
[Arguments(123456789)]
[Arguments(1234567890)]
public string UInt32ToString(uint value) => value.ToString();
[Benchmark]
[Arguments(1)]
[Arguments(12)]
[Arguments(123)]
[Arguments(1234)]
[Arguments(12345)]
[Arguments(123456)]
[Arguments(1234567)]
[Arguments(12345678)]
[Arguments(123456789)]
[Arguments(1234567890)]
public bool UInt32TryFormat(uint value) => value.TryFormat(_scratch, out _);
[Benchmark]
[Arguments(1, "D")]
[Arguments(255, "D3")]
[Arguments(12345, "D5")]
[Arguments(1234567890, "D10")]
public bool UInt32TryFormatSpecifier(uint value, string format) => value.TryFormat(_scratch, out _, format);
[Benchmark]
[Arguments(1)]
[Arguments(12)]
[Arguments(123)]
[Arguments(1234)]
[Arguments(12345)]
[Arguments(123456)]
[Arguments(1234567)]
[Arguments(12345678)]
[Arguments(123456789)]
[Arguments(1234567890)]
[Arguments(12345678901)]
[Arguments(123456789012)]
[Arguments(1234567890123)]
[Arguments(12345678901234)]
[Arguments(123456789012345)]
[Arguments(1234567890123456)]
[Arguments(12345678901234567)]
[Arguments(123456789012345678)]
[Arguments(1234567890123456789)]
[Arguments(12345678901234567890)]
public string UInt64ToString(ulong value) => value.ToString();
[Benchmark]
[Arguments(1)]
[Arguments(12)]
[Arguments(123)]
[Arguments(1234)]
[Arguments(12345)]
[Arguments(123456)]
[Arguments(1234567)]
[Arguments(12345678)]
[Arguments(123456789)]
[Arguments(1234567890)]
[Arguments(12345678901)]
[Arguments(123456789012)]
[Arguments(1234567890123)]
[Arguments(12345678901234)]
[Arguments(123456789012345)]
[Arguments(1234567890123456)]
[Arguments(12345678901234567)]
[Arguments(123456789012345678)]
[Arguments(1234567890123456789)]
[Arguments(12345678901234567890)]
public bool UInt64TryFormat(ulong value) => value.TryFormat(_scratch, out _);
[Benchmark]
[Arguments(1ul, "D")]
[Arguments(255ul, "D3")]
[Arguments(12345ul, "D5")]
[Arguments(1234567890, "D10")]
[Arguments(12345678901234567890ul, "D20")]
public bool UInt64TryFormatSpecifier(ulong value, string format) => value.TryFormat(_scratch, out _, format);
|
danmoseley
reviewed
Oct 6, 2022
src/libraries/System.Private.CoreLib/src/System/Buffers/Text/FormattingHelpers.CountDigits.cs
Outdated
Show resolved
Hide resolved
jkotas
reviewed
Oct 7, 2022
src/libraries/System.Private.CoreLib/src/System/Buffers/Text/FormattingHelpers.CountDigits.cs
Outdated
Show resolved
Hide resolved
tannergooding
approved these changes
Oct 10, 2022
This was referenced Oct 10, 2022
This was referenced Oct 13, 2022
Closed
Closed
Closed
This was referenced Nov 3, 2022
Closed
Closed
Closed
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to subscribe to this conversation on GitHub.
Already have an account?
Sign in.
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Follow-up to #76519 (comment)
The numbers from thesebenchmarks are almost all wins. The main exception is that the TryFormat, 1 digit, no format specifier case gets a little slower... it's repeatable on my machine, but also sub-ns.