Skip to content
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

Fix perf regressions in Utf8Formatter for integers #85277

Merged
merged 1 commit into from
Apr 25, 2023

Conversation

stephentoub
Copy link
Member

When I added UTF8 support to the core numeric types, I also just routed Utf8Formatter to use the public TryFormat API on each type. That, however, regressed some microbenchmarks due to a) going from StandardFormat to a ReadOnlySpan<char> format and then parsing it back out and b) removing some of the inlining that was there previously. This change puts back into Utf8Formatter.TryFormat the handling of the format and then delegating to the relevant helpers that already exist rather than always going through the public entrypoint (it doesn't do so for 'n', but that's also much rarer to use on a hot path and is also in general more expensive).

private byte[] _buffer = new byte[100];

[Benchmark]
[Arguments(42)]
public bool TryFormatInt32_Default(int value) => Utf8Formatter.TryFormat(value, _buffer, out _);

[Benchmark]
[Arguments(123L)]
public bool TryFormatInt64_Default(long value) => Utf8Formatter.TryFormat(value, _buffer, out _);

[Benchmark]
[Arguments(12345UL)]
public bool TryFormatUInt64_Default(ulong value) => Utf8Formatter.TryFormat(value, _buffer, out _);

[Benchmark]
[Arguments(42)]
public bool TryFormatInt32_D2(int value) => Utf8Formatter.TryFormat(value, _buffer, out _, new StandardFormat('D', 2));

[Benchmark]
[Arguments(0x1234UL)]
public bool TryFormatUInt64_X4(ulong value) => Utf8Formatter.TryFormat(value, _buffer, out _, new StandardFormat('X', 4));

[Benchmark]
[Arguments(long.MinValue)]
public bool TryFormatInt64_X(long value) => Utf8Formatter.TryFormat(value, _buffer, out _, new StandardFormat('X'));
Method Runtime value Mean Error StdDev Ratio Code Size
TryFormatInt64_X main -9223372036854775808 29.905 ns 0.6288 ns 1.0154 ns 1.51 2,545 B
TryFormatInt64_X pr -9223372036854775808 13.156 ns 0.2904 ns 0.4607 ns 0.67 210 B
TryFormatInt64_X .NET 7.0 -9223372036854775808 19.803 ns 0.4241 ns 0.6082 ns 1.00 294 B
TryFormatInt64_Default main 123 10.610 ns 0.1336 ns 0.1250 ns 1.62 974 B
TryFormatInt64_Default pr 123 8.754 ns 0.1467 ns 0.1225 ns 1.34 1,167 B
TryFormatInt64_Default .NET 7.0 123 6.558 ns 0.1536 ns 0.1508 ns 1.00 829 B
TryFormatUInt64_Default main 12345 10.376 ns 0.2275 ns 0.2337 ns 1.41 608 B
TryFormatUInt64_Default pr 12345 5.027 ns 0.0532 ns 0.0498 ns 0.68 318 B
TryFormatUInt64_Default .NET 7.0 12345 7.379 ns 0.0931 ns 0.0825 ns 1.00 458 B
TryFormatInt32_Default main 42 9.973 ns 0.0832 ns 0.0737 ns 1.78 964 B
TryFormatInt32_Default pr 42 2.933 ns 0.0472 ns 0.0442 ns 0.52 348 B
TryFormatInt32_Default .NET 7.0 42 5.609 ns 0.0647 ns 0.0574 ns 1.00 829 B
TryFormatInt32_D2 main 42 20.623 ns 0.1623 ns 0.1518 ns 3.26 2,438 B
TryFormatInt32_D2 pr 42 4.125 ns 0.1098 ns 0.0917 ns 0.65 573 B
TryFormatInt32_D2 .NET 7.0 42 6.340 ns 0.0438 ns 0.0366 ns 1.00 581 B
TryFormatUInt64_X4 main 4660 21.726 ns 0.4386 ns 0.3662 ns 3.69 2,030 B
TryFormatUInt64_X4 pr 4660 6.411 ns 0.0568 ns 0.0474 ns 1.09 257 B
TryFormatUInt64_X4 .NET 7.0 4660 5.841 ns 0.1648 ns 0.1897 ns 1.00 290 B

When I added UTF8 support to the core numeric types, I also just routed Utf8Formatter to use the public TryFormat API on each type.  That, however, regressed some microbenchmarks due to a) going from `StandardFormat` to a `ReadOnlySpan<char>` format and then parsing it back out and b) removing some of the inlining that was there previously.  This change puts back into Utf8Formatter.TryFormat the handling of the format and then delegating to the relevant helpers that already exist rather than always going through the public entrypoint (it doesn't do so for 'n', but that's also much rarer to use on a hot path and is also in general more expensive).
@stephentoub stephentoub added this to the 8.0.0 milestone Apr 24, 2023
@ghost
Copy link

ghost commented Apr 24, 2023

Tagging subscribers to this area: @dotnet/area-system-memory
See info in area-owners.md if you want to be subscribed.

Issue Details

When I added UTF8 support to the core numeric types, I also just routed Utf8Formatter to use the public TryFormat API on each type. That, however, regressed some microbenchmarks due to a) going from StandardFormat to a ReadOnlySpan<char> format and then parsing it back out and b) removing some of the inlining that was there previously. This change puts back into Utf8Formatter.TryFormat the handling of the format and then delegating to the relevant helpers that already exist rather than always going through the public entrypoint (it doesn't do so for 'n', but that's also much rarer to use on a hot path and is also in general more expensive).

private byte[] _buffer = new byte[100];

[Benchmark]
[Arguments(42)]
public bool TryFormatInt32_Default(int value) => Utf8Formatter.TryFormat(value, _buffer, out _);

[Benchmark]
[Arguments(123L)]
public bool TryFormatInt64_Default(long value) => Utf8Formatter.TryFormat(value, _buffer, out _);

[Benchmark]
[Arguments(12345UL)]
public bool TryFormatUInt64_Default(ulong value) => Utf8Formatter.TryFormat(value, _buffer, out _);

[Benchmark]
[Arguments(42)]
public bool TryFormatInt32_D2(int value) => Utf8Formatter.TryFormat(value, _buffer, out _, new StandardFormat('D', 2));

[Benchmark]
[Arguments(0x1234UL)]
public bool TryFormatUInt64_X4(ulong value) => Utf8Formatter.TryFormat(value, _buffer, out _, new StandardFormat('X', 4));

[Benchmark]
[Arguments(long.MinValue)]
public bool TryFormatInt64_X(long value) => Utf8Formatter.TryFormat(value, _buffer, out _, new StandardFormat('X'));
Method Runtime value Mean Error StdDev Ratio Code Size
TryFormatInt64_X main -9223372036854775808 29.905 ns 0.6288 ns 1.0154 ns 1.51 2,545 B
TryFormatInt64_X pr -9223372036854775808 13.156 ns 0.2904 ns 0.4607 ns 0.67 210 B
TryFormatInt64_X .NET 7.0 -9223372036854775808 19.803 ns 0.4241 ns 0.6082 ns 1.00 294 B
TryFormatInt64_Default main 123 10.610 ns 0.1336 ns 0.1250 ns 1.62 974 B
TryFormatInt64_Default pr 123 8.754 ns 0.1467 ns 0.1225 ns 1.34 1,167 B
TryFormatInt64_Default .NET 7.0 123 6.558 ns 0.1536 ns 0.1508 ns 1.00 829 B
TryFormatUInt64_Default main 12345 10.376 ns 0.2275 ns 0.2337 ns 1.41 608 B
TryFormatUInt64_Default pr 12345 5.027 ns 0.0532 ns 0.0498 ns 0.68 318 B
TryFormatUInt64_Default .NET 7.0 12345 7.379 ns 0.0931 ns 0.0825 ns 1.00 458 B
TryFormatInt32_Default main 42 9.973 ns 0.0832 ns 0.0737 ns 1.78 964 B
TryFormatInt32_Default pr 42 2.933 ns 0.0472 ns 0.0442 ns 0.52 348 B
TryFormatInt32_Default .NET 7.0 42 5.609 ns 0.0647 ns 0.0574 ns 1.00 829 B
TryFormatInt32_D2 main 42 20.623 ns 0.1623 ns 0.1518 ns 3.26 2,438 B
TryFormatInt32_D2 pr 42 4.125 ns 0.1098 ns 0.0917 ns 0.65 573 B
TryFormatInt32_D2 .NET 7.0 42 6.340 ns 0.0438 ns 0.0366 ns 1.00 581 B
TryFormatUInt64_X4 main 4660 21.726 ns 0.4386 ns 0.3662 ns 3.69 2,030 B
TryFormatUInt64_X4 pr 4660 6.411 ns 0.0568 ns 0.0474 ns 1.09 257 B
TryFormatUInt64_X4 .NET 7.0 4660 5.841 ns 0.1648 ns 0.1897 ns 1.00 290 B
Author: stephentoub
Assignees: -
Labels:

area-System.Memory, tenet-performance

Milestone: 8.0.0

@stephentoub stephentoub merged commit 759fabe into dotnet:main Apr 25, 2023
162 of 167 checks passed
@stephentoub stephentoub deleted the fixutf8formatterperf branch April 25, 2023 02:51
@ghost ghost locked as resolved and limited conversation to collaborators May 25, 2023
@DrewScoggins
Copy link
Member

Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

4 participants