Improve throughput of UInt32/UInt64.{Try}Parse by stephentoub · Pull Request #18930 · dotnet/coreclr (original) (raw)
As was recently done for Int32/Int64 in #18897, tjis ports the Utf8Parser approach to parsing to UInt32/UIn64.{Try}Parse, specifically for NumberStyles.Integer (the default).
Also fixes an issue discovered in the previous Int32/Int64 changes, where if the input both has an overflow and has a formatting error (e.g. Int32.Parse("12345678910blah"), we would end up throwing whichever error was hit first, which is a change from .NET Core 2.1 and netfx. The FormatException needs to be preferred over the OverflowException, which just means we can't bail early when overflow is detected. I'm putting up another corefx tests PR with more tests that cover this.
(I tried to stay fairly true to the Utf8Parser code, since time and effort was spent tuning those. However, this has yielded a fair amount of duplication now, with code repeated four times for each of Int32, UInt32, Int64, and UInt64. After all of the perf improvements are in, we should look to see whether we can retain almost all of the gains while reducing some of that duplication.)
Contributes to https://github.com/dotnet/corefx/issues/30612
cc: @jkotas, @ahsonkhan, @danmosemsft
Benchmark:
using System; using BenchmarkDotNet.Attributes; using BenchmarkDotNet.Attributes.Jobs; using BenchmarkDotNet.Running;
[MemoryDiagnoser] [InProcess] public class Benchmark { private static void Main() => BenchmarkRunner.Run();
[Benchmark] public uint UInt32Parse1() => uint.Parse("1");
[Benchmark] public uint UInt32Parse12345() => uint.Parse("12345");
[Benchmark] public uint UInt32Parse1234567890() => uint.Parse("1234567890");
[Benchmark] public uint UInt32ParseW0W() => uint.Parse(" 0 ");
[Benchmark] public bool UInt32TryParseInvalid() => uint.TryParse("123a", out _);
[Benchmark] public ulong UInt64Parse1() => ulong.Parse("1");
[Benchmark] public ulong UInt64Parse123456789() => ulong.Parse("123456789");
[Benchmark] public ulong UInt64Parse12345678901234567890() => ulong.Parse("12345678901234567890");
[Benchmark] public ulong UInt64ParseW0W() => ulong.Parse(" 0 ");
[Benchmark] public bool UInt64TryParseInvalid() => ulong.TryParse("123a", out _);
}
Before/After:
Benchmark | Before (ns) | After (ns) | Improvement |
---|---|---|---|
UInt32Parse1 | 73.78 | 32.13 | 2.30x |
UInt32Parse12345 | 91.89 | 35.83 | 2.56x |
UInt32Parse1234567890 | 127.03 | 38.99 | 3.26x |
UInt32ParseW0W | 79.15 | 33.25 | 2.38x |
UInt32TryParseInvalid | 89.62 | 42.02 | 2.13x |
UInt64Parse1 | 80.13 | 33.08 | 2.42x |
UInt64Parse123456789 | 117.99 | 41.03 | 2.88x |
UInt64Parse12345678901234567890 | 181.83 | 51.02 | 3.56x |
UInt64ParseW0W | 73.44 | 36.77 | 2.00x |
UInt64TryParseInvalid | 83.99 | 42.6 | 1.97x |
(Note that these numbers are from a different machine than the numbers in the Int32/Int64 PR, so they're not directly comparable to each other.)