Skip to content
This repository was archived by the owner on Jan 23, 2023. It is now read-only.

Remove use of Unsafe in UTF-8 formatters, plus performance improvements - #26289

Merged
GrabYourPitchforks merged 8 commits into
dotnet:masterfrom
GrabYourPitchforks:levib/remove_unsafe_4
Jan 19, 2018
Merged

Remove use of Unsafe in UTF-8 formatters, plus performance improvements#26289
GrabYourPitchforks merged 8 commits into
dotnet:masterfrom
GrabYourPitchforks:levib/remove_unsafe_4

Conversation

@GrabYourPitchforks

@GrabYourPitchforks GrabYourPitchforks commented Jan 12, 2018

Copy link
Copy Markdown
Member

Fixes https://github.com/dotnet/corefx/issues/25648.

My first attempt to switch the implementations from unsafe code to safe buffer-based code was a bit naive and resulted in considerable performance degradation (double-digit percentage loss of throughput across many APIs). I've spent time reworking the implementations to to work around the performance loss, and in many cases the new implementations are faster than the originals.

For reviewers: I recommend looking at each commit in isolation, as each commit deals with a very specific formatter. It'll also be easier to see which helper routines in FormatterHelpers correlate with which implementations. This also allows individual commits to be backed out without affecting the rest of the PR if reviewers deem a particular commit as unwanted.

Also optimize code: on Win10 amd64 this results in +46% throughput (GUIDs formatted per second)
On Win10 amd64 this results in +82% throughput (bools formatted per second)
Does not significantly impact perf; measurements are within +/-5% on Win10 amd64 test box
Also optimize code: on Win10 amd64 this results in +25% to +35% throughput (DateTimes formatted per second) depending on format
Also add missing test case for DateTimeOffset formatter
Also optimize code: on Win10 amd64 this results in +20% to +30% throughput (TimeSpans formatted per second) depending on magnitude of value
Also optimize code: on Win10 amd64 this results in +45% throughput (integers formatted per second) for signed integers formatted as D or N, +60% throughput for unsigned integers formatted as D or N
if (format.HasPrecision)
throw new NotSupportedException(SR.Argument_GWithPrecisionNotSupported); // With a precision, 'G' can produce exponential format, even for integers.
return TryFormatUInt64D(value, format.Precision, buffer, out bytesWritten);
return TryFormatUInt64D(value, format.Precision, buffer, false /* insertNegationSign */, out bytesWritten);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

C# now allows name-tagging arguments without name-tagging every argument after it:

return TryFormatUInt64D(value, format.Precision, buffer, false /* insertNegationSign */, out bytesWritten);

==>

return TryFormatUInt64D(value, format.Precision, buffer, insertNegationSign: false, out bytesWritten);

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Didn't know that - nifty! :)

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is now addressed.

@ghost

ghost commented Jan 12, 2018

Copy link
Copy Markdown

What are the code coverage numbers after this change?

{
bytesWritten = 0;
return false;
const uint FalsValueUppercase = ('F' << 24) + ('a' << 16) + ('l' << 8) + ('s' << 0);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: "Fals" -> "False"

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nvm - I see the "e" doesn't fit into a uint :-)

ThrowHelper.ThrowArgumentException_InvalidTypeWithPointersNotSupported(typeof(T));
}
#endif
return Span<byte>.DangerousCreate(null, ref Unsafe.As<T, byte>(ref value), Unsafe.SizeOf<T>());

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is not valid - it crash with slow Span.

It is not valid to pass null as first argument to DangerousCreate,

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Previous closed issue about this https://github.com/dotnet/corefx/issues/26124

This provides approximately equal (within noise) performance to the original code which turned a ref Guid into a Span<byte>
@GrabYourPitchforks

Copy link
Copy Markdown
Member Author

@atsushikan I don't have code coverage numbers available. I'm riding on top of the existing (quite comprehensive) unit tests for this functionality, plus I added a few extra test cases for situations where the original code produced incorrect results.

@ghost

ghost commented Jan 16, 2018

Copy link
Copy Markdown

@GrabYourPitchforks - You can get code coverage results by doing this:

   cd <repo>\src\System.Memory
   msbuild /t:build /p:DebugType=pdbonly
   cd <repo>\src\System.Memory\tests

   msbuild -t:clean;build;test -p:Coverage=true
   start <repo>\bin\tests\coverage\index.htm

The coverage numbers for Utf8Formatting were at a 100% when I checked in the original stuff - it's probably regressed some since then but it'd be good to know at least that the stuff you touched is at 100%.

@MattGal

MattGal commented Jan 16, 2018

Copy link
Copy Markdown
Member

@dotnet-bot test Linux x64 Release Build please
@dotnet-bot test OSX x64 Debug Build please
@dotnet-bot test UWP CoreCLR x64 Debug Build please
@dotnet-bot test Windows x64 Debug Build please
@dotnet-bot test Windows x86 Release Build please

@GrabYourPitchforks

Copy link
Copy Markdown
Member Author

@atsushikan It has regressed slightly (89%), but those were changes separate from this PR. Everything looks good here from a code coverage perspective. :)

else if (symbol == 'l')
{
const uint TrueValueLowercase = ('t' << 24) + ('r' << 16) + ('u' << 8) + ('e' << 0);
if (!BinaryPrimitives.TryWriteUInt32BigEndian(buffer, TrueValueLowercase))

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would writing TryFormat using BinaryPrimitives still be necessary once TryCopyTo is optimized?
https://github.com/dotnet/coreclr/issues/15076

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

{
for (int i = FractionDigits; i > digitCount; i--)
value /= 10;
// This is a faster implementation of Span<T>.Fill().

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why is this faster? Is it due to buffer.Length being too small in this case?

Wouldn't Fill using InitBlockUnaligned be just as fast, if not faster:
https://github.com/dotnet/corefx/blob/master/src/System.Memory/src/System/Span.cs#L219-L231

using System.Runtime.CompilerServices;

#if !netstandard
using Internal.Runtime.CompilerServices;

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we still need this using directive?

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Removed it here: #26598

@karelz karelz added this to the 2.1.0 milestone Jan 20, 2018
@GrabYourPitchforks
GrabYourPitchforks deleted the levib/remove_unsafe_4 branch January 29, 2018 02:27
picenka21 pushed a commit to picenka21/runtime that referenced this pull request Feb 18, 2022
…ts (dotnet/corefx#26289)

Remove use of Unsafe in UTF-8 formatters, plus performance improvements

Commit migrated from dotnet/corefx@534af93
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

6 participants