-
-
Notifications
You must be signed in to change notification settings - Fork 965
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
feat: Add numerics type conversions #2238
Conversation
I think if you pass by ref (use |
I did some quick tests with Inlining and the performance was the same or similar on a few runs. It seems like the compiler does not make any significant changes with or witthout it to the compiled code, I tested with the Bepu library mentioned above. Unless Im missing something I cant seem to use |
As you can see, the jit tends to inline such a small function regardless of the attribute. |
Ah nice, BitCast looks awesome. I was imagining it like this, which it looks like BitCast creates the same code in a cleaner way. public static Vector2 ToStride(this in NVector2 v) => Unsafe.As<NVector2, Vector2>(ref Unsafe.AsRef(v)); The
The assembly for C.Bitcast(System.Numerics.Vector2)
L0000: vzeroupper
L0003: vmovq xmm0, rcx
L0008: vmovq rax, xmm0
L000d: ret
C.BitcastIn(System.Numerics.Vector2 ByRef)
L0000: mov rax, [rcx]
L0003: ret |
I went ahead and added both for user convenience. There have already been many complaints in some of the existing libraries for confusion with the reference based Stride math so hopefully this will avoid confusion for users who just need the fast conversion even if they are missing out on a little bit of extra performance. Also sorry for the late update, I have promised way to much with very little time but this was an easy fix I remembered about today 😆 |
Nevermind on having both! I forgot that extensions dont care and the user should not notice it when converting. |
Should we implement this as an explicit conversion operator instead ? |
#1161 This is the only reason I am hesitant, I dont know what could have broken for other platforms due to this PR. Otherwise I loved the idea since it was by far the simplest to use from a user perspective. |
RyuJIT doesn't recognize Stride's vectors as vectors. They are considered structs with 4-byte fields and the assembly generated for them reflects that. There is not a relevant difference between using pointers, refs, or reinterpretation of bits with bitcast. Still a method body using It could be a serious boon to performance to add fields of type After which the conversion is just reading the vector-sized field and no byref, pointer or bitcast is necessary at all. |
Would it make sense to have the struct just be a wrapper around a private field of type System.Numerics? That way all internal math would be done on System.Numerics types. public struct Vector2
{
[DataMember(1)]
public float X
{
get
{
return _internalVector.X;
}
set
{
_internalVector.X = value;
}
}
[DataMember(1)]
public float Y
{
get
{
return _internalVector.Y;
}
set
{
_internalVector.Y = value;
}
}
private System.Numerics.Vector2 _internalVector;
} Just spitballing for potential solutions of moving towards System.Numerics I did not run this through any tests to prove it would be better in performance. |
That is the general idea, although changing So what I meant to do is like this: [StructLayout(LayoutKind.Explicit)]
public struct Vector2 {
[FieldOffset(0)] private System.Numerics.Vector2 v;
[FieldOffset(0)] public float X;
[FieldOffset(4)] public float Y;
} With the added benefit that it will immediately compile all existing code and the move to use |
For the matrix PR, I planned on having it as a baked field offset as well, issue was that the jited asm for constructor append a setter for that field as well even if the rest of the setter takes care of setting the bytes of the other fields overlapping that one. So we do accelerate access to the numerics but we may introduce some performance degradation for ctor. Vector2 d;
d.X = 10;
d.Y = 20;
return d; |
This case is what In the example above one constructor would become: Vector2(float x, float y) {
X = x;
Y = y;
Unsafe.SkipInit(out v);
} This sort of code would have to be repeated everywhere the compiler complains. And if you mean Stride users can't do that, since |
Right, sounds good, although, if there had to be breaking changes related to vectors, I would rather we introduce them all at once. We could add this optimization in as part of the move to numerics/silk math instead ? |
If the change is from your four-liner to The suggestion here was instead of On the general move from Stride's math to the builtin types, I would start yesterday with deprecations since not doing those keeps everything around for year three in a row. Whether with the extra field - which I wouldn't hesitate to make public either - or with more verbosity literally everywhere, the smoothest path is to just fix the old, non-vectorized code, perhaps even add some API's, so as to make an eventual transition nearly entirely code compatible and of equal performance. |
@ericwj sure, but stride doesn't just target programmers, and some of the programmers are just starting out. We can merge this in with a bitcast to have a non-breaking version and have another PR marked for 4.3 replacing the bitcast ?
We're all stuck on other areas right now, feel free to start working on that if you want to :) |
I don't think there is any way around either a 4.3/5.0 release for a breaking change especially involving math. I also have another PR being worked on that will also be a pretty big breaking change for game start up including windowing so maybe we should start pooling these together as potential goals? Some sort of tagging or project tracking maybe @VaclavElias already started something like that? Whichever is decided I have been trying to keep track of these conversations in discussions as much as possible. I feel like one of the issues with these conversations seeming to pop up often over a long period of time is with the part time nature of all of the devs. Coming back after breaks things are often forgotten or lost to the bowels of Discord or scatterd in issues/PRs. |
…into add-math-extensions
…into add-math-extensions
@Eideren The implicit conversions should be there from Ykafia, I left in the extensions as well but I can remove them if the implicit conversions work. |
All green on TC's side, awaiting your last change and @ericwj 's feedback |
Sweet, Ill remove the extensions today then. Should I move the implicit conversions to |
You can wait for Eric's feedback if you would rather not come back to this one twice, otherwise feel free to swap to bitcast. |
Im ok to come back and change again if needed, this is a pretty small PR for me to make changes to if I need to. |
added quaternion implicit moved to BitCast
/// Casts from System.Numerics to Stride.Maths matrix | ||
/// </summary> | ||
/// <param name="v">Value to cast</param> | ||
public static implicit operator Matrix(System.Numerics.Matrix4x4 v) |
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 is in fact a transpose followed by a cast. Better to write it like that, remove the code duplication. Better yet, while you're at it, cast it first, then transpose, so the transpose is optimized. Little effort to then go and replace the Transpose
method doing it like that, too, with 2 casts - which should both be free.
Yes. Sure. With regards to the method of doing the cast. There is no difference between
I believe still the promise is that there are no breaking changes unless the API has been deprecated for a year, or one whole release cycle - not sure of the exact wording here. That is why the deprecations are so important to do. |
Transposing with Stride and using the Stride subtract method for the benchmark public static SMatrix NumericToStrideUnsafe(this NMatrix v)
{
SMatrix nm = Unsafe.As<NMatrix, SMatrix>(ref v);
nm.Transpose();
return nm;
} transposing with Numerics before casting and using the Numerics subtract method for the benchmark public static SMatrix NumericToStrideUnsafe(this NMatrix v)
{
v = NMatrix.Transpose(v);
SMatrix nm = Unsafe.As<NMatrix, SMatrix>(ref v);
return nm;
} That's quite the difference... I guess that's what you were saying with the 2 casts in the Stride transpose as well? |
Doing a few more tests, I think I see what you mean with the methods we can deprecate and maybe even just use Numerics math for the extra benefits without things breaking or even changing for the end user. I'll save it for a second PR but I have another thing I can work towards for #2576 with a much better understanding now. One of the quick tests using the Numerics subtract method is absolutely insane even with the conversion from Stride to Numerics Matrix: [Benchmark]
public void Matrix_TestNumericToStrideWithUnsafeAs()
{
for (int i = 0; i < Iterations; i++)
{
NMatrix.Subtract(SMatrix.StrideToNumericUnsafe(), SMatrix.StrideToNumericUnsafe());
}
} |
So if we convert the math methods to use Numerics as the parameters and use the Numerics math as the underlying method body we can still return a Stride value and gain some benefits. public static Matrix Subtract(MatrixDotnet left, MatrixDotnet right)
{
return MatrixDotnet.Subtract(left, right);
} The 3ms record is with the new subtract method still returning the Stride value and the 6ms is the default Stride subtract method. if we only use Numerics it seems to consistently be 2ms which is insane to me. We could also just return a Numeric value and it seems to be free like Eric was saying but returning a different type might be very confusing to the end user even though it should work the same due to the implicit conversions. |
Yes, although
|
How is that a performance smell? Passing large structs by ref is recommended for performance. Passing by value causes it to be copied. |
The JIT has a specialization for numerics, it shoves them directly in wide registers when passed as arguments afair |
Thanks ! |
PR Details
This PR adds some useful extensions that are commonly used with external libraries since Numerics has become the standard.
Related Issue
#2131 this is a great example where something like this could be used.
Motivation and Context
the Bepu branch has this built into the library but its also used in the model importer as well as any library that may need these fast conversions.
Types of changes
Checklist