-
Notifications
You must be signed in to change notification settings - Fork 4.8k
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
Improve performance of integer formatting #76726
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);
|
src/libraries/System.Private.CoreLib/src/System/Buffers/Text/FormattingHelpers.CountDigits.cs
Outdated
Show resolved
Hide resolved
src/libraries/System.Private.CoreLib/src/System/Buffers/Text/FormattingHelpers.CountDigits.cs
Outdated
Show resolved
Hide resolved
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.
Nice!
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.