-
Notifications
You must be signed in to change notification settings - Fork 4.7k
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
Intrinsicify (Sse, Axv2, Arm64, wasm) JsonReaderHelper.IndexOfOrLessThan #41097
Conversation
x64 performance changes (up to +20% improvement)
|
@benaadams - Do we actually need to cross compile for Broswer to make this kind of change? Blazor WASM apps are run through the ILLinker during publish. At link-time, the ILLinker is passed that So I think you should naturally write the code as you typically would: if (Sse2.IsSupported || AdvSimd.Arm64.IsSupported)
{
// intrinics code
}
else if (Vector.IsHardwareAccelerated)
{
// Vector code
}
else
{
// normal software fallback
} And for a Blazor WASM app, all that will be in the published assembly will be the You don't need to build specifically for Browser to get "size opts". The tooling is set up to make it just work. |
I don't think the wasm likes the |
Adding an intrinsic to the mono interpreter for this method was also looked at #40705, but considered too fragile to do for an upstack lib |
A simple, top-level condition at the root of this method wouldn't get tier-JIT'd away? public static int IndexOfQuoteOrAnyControlOrBackSlash(this ReadOnlySpan<byte> span)
{
if (Sse2.IsSupported || AdvSimd.Arm64.IsSupported || Vector.IsHardwareAccelerated)
{
return IndexOfQuoteOrAnyControlOrBackSlashIntrinsic(span);
}
else
{
return IndexOfQuoteOrAnyControlOrBackSlashForLoop(span);
}
} |
Arm would then go down the |
Arm32? Yes. Is that a concern? |
It was highlighted as an issue #39733 (comment); I assume not regressing platforms when its not too hard to avoid is a desirable feature? /cc @jkotas |
We do support ARM32 and number of our customers are deploying on ARM32. E.g. #41089 is about poor performance of System.Text.Json on Xamarin platforms that means ARM. |
My uber concern here is unnecessarily building more upstack libraries for Maybe this is another case for For |
I do not see this change meeting the bar for backporting to release/5.0. |
We shouldn't be forking this library. Make this a runtime check please. I'm not an expert on the intricacies of getting this right so that the final JIT'ed code is still ideal perf, but other-places that needed some special behavior for interpreter were also using linker substitutions too, right? I'd also like to block on @steveharter's feedback. He's out this week but should be able to take a look next week. |
This is not necessarily true for non-optimized code. Maybe what we need is |
For 6.0 will it run as precompiled wasm? Thus have different considerations? |
That is the plan so far. |
I agree. I'm going to go ahead and move this to 6.0.0. |
@layomia / @eiriktsarpalis can you take a look here? |
@benaadams thoughts on this regression? Fluke or is something else like small payloads? In general, the ~11% mean improvement is great! |
src/libraries/System.Text.Json/src/System/Text/Json/Reader/JsonReaderHelper.cs
Outdated
Show resolved
Hide resolved
src/libraries/System.Text.Json/src/System/Text/Json/Reader/JsonReaderHelper.cs
Show resolved
Hide resolved
955f26f
to
2241f2c
Compare
Rebased and applied feedback |
<ItemGroup Condition="'$(TargetFramework)' == '$(NetCoreAppCurrent)'"> | ||
<Compile Include="System\Text\Json\Reader\JsonReaderHelper.Intrinsics.cs" /> | ||
</ItemGroup> | ||
<ItemGroup Condition="'$(TargetFramework)' != '$(NetCoreAppCurrent)'"> |
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.
Note that many of our libraries that are inbox and OOB do this:
<TargetFrameworks>$(NetCoreAppCurrent);netstandard2.0;netcoreapp3.0;net461</TargetFrameworks>
<ExcludeCurrentNetCoreAppFromPackage>true</ExcludeCurrentNetCoreAppFromPackage>
This means that NetCoreAppCurrent
is the build that goes in the runtimepack/shared framework. But in the NuGet package, only netstandard2.0;netcoreapp3.0;net461
builds get packaged in there. So that means if someone references the OOB System.Text.Json v7 NuGet package, even on net6.0
, they will use the netcoreapp3.0
version from the package, because that's the highest version available.
So I really think these conditions should be '$(TargetFramework)' == '$(NetCoreAppCurrent)' or '$(TargetFramework)' == 'netcoreapp3.0'
- Include .Intrinsics
. Else - include .NoIntrinsics
.
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.
The intrinsics will break on netcoreapp3.0 though because they include the arm variants? Should I include net5.0
and treat that as the netcoreapp3.0 in your example?
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.
This probably needs the experts.
@ericstj @steveharter @layomia - how do you want to handle Arm intrinsics in STJ? Do you want to ship a net5.0
TFM in the OOB package?
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.
Could type forward, but not sure how that works either 😅
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.
Type forwards wouldn't work here. I assume to support Arm we will need a new TFM in the package. But I think that's up to the System.Text.Json owners to decide. I'm just a fly-by reviewer 😄
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.
The OOB packages make your app bigger and slower by design. For example, the OOB package does not come with R2R compiled code, so your app will startup slower when you reference the OOB package.
Not necessarily, I might build self-contained and run cross-gen myself. Or I might be running on an aot runtime.
If you want to depend on latest version, you better update the runtime too.
That's not always an option. Consider what happens in .NET 7.0. We're going to add API to this library and I bet the Azure SDK (or some other libs) will want to use it. At that point any 6.0 app that uses Azure SDK will have no choice but to use the package version.
I'd prefer we keep a simple rule here. If the configuration differs in conditions/ifdefs from the packaged configurations then it should also be included in the package.
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.
At that point any 6.0 app that uses Azure SDK will have no choice but to use the package version.
Yes, it will continue to work - with small perf hit - even if we keep the status quo.
It is ok with me if we want build extra targets to ensure that newer OOB packages on downlevel platforms always get all performance tweaks possible.
There are other assemblies with this problem. For example,
https://github.com/dotnet/runtime/blob/main/src/libraries/System.Text.Encodings.Web/src/System.Text.Encodings.Web.csproj uses ARM intrisics inbox, but there is no OOB build with ARM intrinsics. I assume that we would want to fix all these.
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.
Consider what happens in .NET 7.0. We're going to add API to this library and I bet the Azure SDK (or some other libs) will want to use it. At that point any 6.0 app that uses Azure SDK will have no choice but to use the package version.
Sounds like an issue to resolve before next year's 7.0 release? 😉
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.
Nothing gets resolved by that point. New API in the package means people will use that new API, so consuming the package that's newer than the framework is super common. Folks shouldn't have to pay a penalty when they do that. My position on this is don't use ExcludeCurrentNetCoreAppFromPackage
if there is anything different about the NetCoreAppCurrent
build. Don't split hairs on something saying it's OK because it's only a small perf fix. You might be able to convince yourself of that, but what about the next person that doesn't notice it's excluded? What about the customer that cares about that perf-fix.
The package growth problem is less of an issue now that we're starting to drop out of support frameworks from packages thanks to precedent that @ViktorHofer is setting.
Let's keep our rules simple: if you if'def/conditionally compile, then don't exclude from package.
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.
Regarding the initial feedback, please don't set ExcludeCurrentNetCoreAppFromPackage
anymore as we don't want to exclude the latest .NETCoreApp asset in packages anymore. See #53439 for more details.
As @ericstj mentioned, in the past we excluded specific tfms - even though they were shipping in some form - from our packages but based on the new package support policy we don't carry along unsupported .NETCoreApp assets along anymore which bounds the number of .NETCoreApp assets in a package.
src/libraries/System.Text.Json/src/System/Text/Json/Reader/JsonReaderHelper.Intrinsics.cs
Outdated
Show resolved
Hide resolved
Is there more to do here? |
@@ -0,0 +1,193 @@ | |||
// Licensed to the .NET Foundation under one or more agreements. |
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.
Why two copies of the file, rather than an internal shim for intrinsics that always returns false
/throws
?
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.
Looks like I asked this a while back here: #41097 (comment) and then @steveharter asked for a file split here: #41097 (comment)
I am really not a fan of duplicating the Vector<T>
and fallback logic across two files just so the intrinsic logic can be cleanly separated. The JIT (even full framework) should correctly handle dead code elimination for properties that simply return false
and it will not be pleasant remembering to update both copies of the files for half the code.
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.
Don't see any *.PlatformNotSupported.cs
file references in System.Text.Encodings.Web.csproj?
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.
Looks like @GrabYourPitchforks removed them in #49373
@benaadams Can we get some extra unit test coverage on these APIs to help exercise edge cases in the SIMD / vectorized logic and prevent regressions? In particular, I'd like to see a unit test specifically exercise the We have some types like |
@benaadams based on your timings this is an important PR. Do you plan on responding to @GrabYourPitchforks around potential regressions; if not we can have someone else investigate. Thanks |
The perf numbers look great, but at this point in the release it is risky to add this IMO. We should consider this for 7.0 early. |
Change the output
wasm
forIndexOfOrLessThan
to be a simplefor
loop in C# chosen via packaging as suggested in #40705 (comment)Update the vectorized path to sync with the
SpanHelpers.Byte.cs
method it is a variant of adding intrinsics for Sse, Axv2, Arm64 (upto +20% improvement for ReadJson on x64)/cc @danroth27, @steveharter