Skip to content

Investigate moving the types in System.Numerics.Vectors to use Hardware Intrinsics #956

Description

@tannergooding

Today, hardware acceleration of certain types in the System.Numerics.Vectors project is achieved via [Intrinsic] attributes and corresponding runtime support. There are a few downsides to this approach:

  • Minor tweaks to the backing implementation require shipping a new runtime
  • It is not obvious that the code has a hardware accelerated path (outside reading documentation)
  • Many of the types (such as the Matrix types) are not directly hardware accelerated

In netcoreapp30, the new Hardware Intrinsics feature is supposed to ship. This feature also allows hardware acceleration but at a much more fine-grained level (APIs typically have a 1-to-1 mapping with underlying instructions).

We should investigate moving the types in System.Numerics.Vectors to use hardware intrinsics as this has multiple potential benefits:

  • The hardware acceleration is still tied to the runtime, but minor tweaks can be made without having to ship a new runtime
  • The code having a hardware accelerated path becomes obvious as does the code that will be generated for a given platform/cpu
  • It will become much easier to add hardware acceleration support to types currently missing it (such as Matrix4x4)

Activity

  1. tannergooding commented on Jul 27, 2018

    @tannergooding
    MemberAuthor
  2. tannergooding commented on Jul 27, 2018

    @tannergooding
    MemberAuthor

    I had done some basic/preliminary investigation some time back and the numbers looked promising: https://github.com/dotnet/corefx/issues/25386#issuecomment-361137982

  3. tannergooding commented on Jul 27, 2018

    @tannergooding
    MemberAuthor

    I am marking this up for grabs in case someone from the community wants to work on this. If you want to take a stab, feel free to say so and I can assign the issue out.

    The "Microsoft DirectX Math" library is open-source, MIT-licensed, and has existing HWIntrinsic accelerated code for many of these APIs (but for Native code): https://github.com/microsoft/directxmath. This would likely be a good starting point as the algorithms have been around for a long time, have been in active use in production code, and are known to generally be correct and performant.

    • NOTE: In some cases the C# algorithm and the DirectX algorithm differ slightly, we should make note of these cases and determine the best course of action.
  4. tannergooding commented on Jul 27, 2018

    @tannergooding
    MemberAuthor

    FYI. @eerhardt, @danmosemsft, @CarolEidt, @fiigii

  5. tannergooding commented on Jul 27, 2018

    @tannergooding
    MemberAuthor

    Also CC. @benaadams, who I know has looked at vectorizing/improving some of these types previously

  6. danmoseley commented on Jul 27, 2018

    @danmoseley
    Contributor

    @hughbe in case he is interested in a meaty task.

  7. fiigii commented on Jul 27, 2018

    @fiigii
    Contributor

    @tannergooding Thanks for opening this issue. I agree that porting S.N.Vectors to managed code in HW intrinsic is much better to maintain. But before that, we may need to address some differences between Vector128/256<T> and Vector2/3/4/<T>. For example,

    1. We should have an internal API to convert Vector2/3/4/<T> from/to Vector128/256<T>
    2. Vector128/256<T> are immutable types, but Vector3/4 are mutable (fields of Vector3/4 can be assigned), how can we solve this difference? Or remain the implementation of some operations in the runtime?
  8. fiigii commented on Jul 27, 2018

    @fiigii
    Contributor

    The "Microsoft DirectX Math" library is open-source,

    @tannergooding Would you like to propose a "VectorMath" library for Vector128/256<T>?

  9. 4creators commented on Jul 27, 2018

    @4creators
    Contributor

    IMHO we should take the opportunity when rewriting Vector<T> implementation to:

    1. Use almost complete implementation of Intel HW Intrinsics up to AVX2 to expand Vector API surface with several missing operations which were requested by community or became possible due to HW intrinsics implementation (this would require API proposal, discussion and approval)

    2. Provide Arm64 HW intrinsics support where possible

    cc @CarolEidt @danmosemsft @eerhardt @fiigii @tannergooding

  10. tannergooding commented on Jul 27, 2018

    @tannergooding
    MemberAuthor

    We should have an internal API to convert Vector2/3/4/ from/to Vector128/256

    I don't believe this is needed. You should be able to load a Vector2/3/4 into a Vector128<T>, perform all operations, and then convert back to the return type trivially and without much (if any) overhead.

    Users will also have their own vector/numeric types, so we should focus on making sure that getting a Vector128<T> from a user defined struct is fast/efficient in general.

    Vector128/256 are immutable types, but Vector3/4 are mutable (fields of Vector3/4 can be assigned), how can we solve this difference? Or remain the implementation of some operations in the runtime?

    I'm not sure why you think this is an issue. Could you elaborate?

  11. tannergooding commented on Jul 27, 2018

    @tannergooding
    MemberAuthor

    @tannergooding Would you like to propose a "VectorMath" library for Vector128/256?

    There will likely need to be some minimal helper library that operates solely on Vector128<T> that the Vector2/3/4 code would call into. This would also be useful for other places in the framework that could take advantage of this.

    It would need to be internal at first, and discussions on making it public could happen later.

  12. tannergooding commented on Jul 27, 2018

    @tannergooding
    MemberAuthor

    IMHO we should take the opportunity when rewriting Vector implementation to:

    Any port should be a two/three step process:

    1. Do a clean port of the existing code and make sure it is equally performant
    2. Do a rewrite of the existing APIs to make improvements, where possible
    3. Add new APIs, as required, and implement accordingly

    Doing them together makes it harder to review the code and track the changes.

    ARM/ARM64 support should definitely happen, but only after the APIs have been reviewed/approved/implemented.

  13. fiigii commented on Jul 27, 2018

    @fiigii
    Contributor

    should be able to load a Vector2/3/4 into a Vector128

    That would be great if the runtime can eliminate the round trip. If not, the memory load/store may have considerable overhead when we have __vectorcall in the future.

    Users will also have their own vector/numeric types, so we should focus on making sure that getting a Vector128 from a user defined struct is fast/efficient in general.

    Agree! Related to https://github.com/dotnet/coreclr/issues/19116

  14. tannergooding commented on Jul 27, 2018

    @tannergooding
    MemberAuthor

    That would be great if the runtime can eliminate the round trip.

    I would certainly hope that we can.

    From my view, anything that would prevent us from moving the System.Runtime.Vector project to use exclusively HWIntrinsics (outside the dynamic Vector<T> sizing feature) is a potential perf problem for users wanting to use HWIntrinsics themselves. So we want to get those types of things flagged, investigated, fixed, etc.

  15. saucecontrol commented on Aug 2, 2018

    @saucecontrol
    Member

    anything that would prevent us from moving the System.Runtime.Vector project to use exclusively HWIntrinsics (outside the dynamic Vector sizing feature) is a potential perf problem for users wanting to use HWIntrinsics themselves

    This seems like a good enough excuse for me to take this on if nobody else is interested 😉

    I'm at a bit of a loss how to start, though. Even something as trivial as operator + on Vector2 is twisting my brain. As an intrinsic, the JIT knows whether the struct is currently in an xmm register and can emit just a addps. If it's not in a register, it loads it with movsd before emitting the addps. Assuming I pin this and take its address so I can explicitly load it with Sse2.LoadScalarVector128(double*), is the JIT containment logic smart enough to know it's already in a xmm register and skip that part? And would it do the same with Vector3 where the load process is movsd + movss + insertps?

    The constructors are even more confusing to me since they may be a setup for further SIMD operations or the fields may be immediately accessed. The JIT seems to know what to do about that now, but I can't imagine how managed code could get that right.

    As far as the porting process, would the goal be to replace all [intrinsic] methods first and then SIMD-ize the things that aren't using intrinsics today?

  16. 27 remaining items

  17. fiigii commented on Dec 18, 2018

    @fiigii
    Contributor

    JIT was still treating the methods as intrinsic.

    Ah, could you try to remove those ones from simdintrinsiclist.h?

  18. tannergooding commented on Dec 18, 2018

    @tannergooding
    MemberAuthor

    Yes, that works. However, it requires modifying CoreCLR every iteration (longer inner loop) and impacts more than just the singular method a person may be working on (for example, the single Equals entry in simdintrinsiclist.h impacts the Equals method on Vector2, Vector3, Vector4, Vector, and Vector<T>). It also leaves the System.Numerics.Vectors assembly in an odd state where you can't determine what can or cannot be an intrinsic by looking at the source.

    I'd rather get the types actually respecting the Intrinsic attribute first and then continue looking at swapping things out a method and type at a time afterwards.

  19. fiigii commented on Dec 18, 2018

    @fiigii
    Contributor

    I'd rather get the types actually respecting the Intrinsic attribute first and then continue looking at swapping things out a method and type at a time afterwards.

    Agree.

  20. transferred this issue fromdotnet/corefxon Dec 16, 2019
  21. added this to the Future milestone on Dec 16, 2019
  22. tannergooding commented on Mar 4, 2020

    @tannergooding
    MemberAuthor

    I had done an initial investigation of this in dotnet/coreclr#27483, but it was closed due to generating more complex trees and therefore missing some optimizations. This is not something that could be readily handled today.

    @CarolEidt, @jkotas, @AndyAyersMS.

    Unless you have any objections, I plan on closing this as unactionable until the blocking issues are addressed. Do we have issues currently tracking the needed improvements?

    As an aside, with us looking at introducing more AOT/R2R options, the ISA specific paths codified in the JIT are becoming more problematic and we are needing to block more methods due to this (e.g. #33090). This also means consumers can't see the benefits of the ISA specific perf improvements when they are available. On the other hand, ISA specific paths codified in managed code don't have this problem and we are able to support both paths relatively trivially. We also have better codegen support in general (especially around containment). and so addressing some of the currently blocking issues may become more necessary as we move forward.

  23. jkotas commented on Mar 4, 2020

    @jkotas
    Member

    I plan on closing this as unactionable until the blocking issues are addressed

    I think that this can be kept open to keep track of the progress. It is something we want to do, it just cannot be done today.

    Agree with the rest.

  24. added
    blockedIssue/PR is blocked on something - see comments
    and removed
    untriagedNew issue has not been triaged by the area owner
    on Mar 5, 2020
  25. tannergooding commented on Jan 15, 2021

    @tannergooding
    MemberAuthor

    Going to close this as most off the SIMD intrinsics have already been ported to use HWIntrinsics internally during importation.

  26. ghost locked as resolved and limited conversation to collaborators on Feb 14, 2021
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

Type

No type

Projects

No projects

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions