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
[API Proposal] Support for constructing and copying Vector<T> from pointers #16026
Comments
I wonder whether we actually want to expose the |
I think I understand where you're coming from, but I don't think it's an issue, actually. The constructor copies the data out of the pointer's location, it doesn't actually wrap it. By the time you return the public unsafe Vector<float> GetRandomFloats()
{
Random r = new Random();
float* data = stackalloc float[Vector<float>.Count];
for (int i = 0; i < Vector<float.Count; i++)
data[i] = (float)r.NextDouble();
return new Vector<float>(data);
}
|
It copies the data??
EDIT: Oh, wait, I see, |
It doesn't copy the data into an object - it copies the data into a struct. Which, with the intrinsic, will usually mean copying the data from memory (wherever it's been allocated) into a SIMD register. As for how much to copy, it will always copy the length of a Vector, which the caller would presumably ensure is accessible. |
Yes, I was misunderstanding the design of |
Carol put it better than I did, I think. I will include that information in the proposal because it's important. Another thing that might help explain why these will be useful: Because
Consider float* data = stackalloc float[4];
Vector4 vec = *(Vector4*)data;
Vector4* vectorData = stackalloc Vector4[100];
Vector4 first = vectorData[0];
float* xPtr = &first.X;
// etc. Because |
@stephentoub I assume the comments from @CarolEidt and @mellinoe clarified it. Are you still opposed to the design? |
No, it's fine. |
Couple of questions:
|
Also, tagging a few more folks that might have input. If possible, we should try to conclude it here, as opposed to having a meeting. |
We don't need them, but it would let you stay out of unsafe code if you already have an
Right now, .NET Native doesn't support the intrinsics at all, as far as I'm aware. It's only available on RyuJIT, and LLILC has some support for it as well.
The support is actually from RyuJIT, so it will work on the (x64) desktop CLR when the JIT is refreshed in the next framework update. Until then, they will work but not be intrinsics. I believe that the |
I should probably know the answer to this, but would void* vs IntPtr be distinct overloads, since (I think) both would map to native int in the IL? |
The void* overload is not yet recognized by the JIT, but these would be very easy to add. |
They will be distinct, since you can have both overloads. Compiling this: public unsafe void VoidPtr(void* ptr)
{
}
public unsafe void IntPtr(IntPtr ptr)
{
} gives this IL (from ILSpy):
|
Any more opinions regarding this? I'd like to get this in for RC2 but the deadline for that is quickly approaching. I'll have the managed-side implementation complete sometime in the next day or so (just need to add the |
Update: My working branch now has all of the proposed overloads implemented. On the manged side, the IntPtr overloads just delegate to the void* overloads. |
Does it make sense to wait for |
Just to follow up on the api review; fine without the If safety checks were desirable, taking Though if you are going unsafe people like no bounds checks and in However, it might make sense for the e.g.
As per precedent with Buffer.MemoryCopy
For our use case it would be |
@krwq, I don't think we should block on Span. It's unclear when we will get it and also we already have so many APIs in the framework done the "old way" that adding one more won't make a substantial difference. |
We've approved the API with feedback. Details in the notes. |
We would love this for use in image processing, where we always have unmanaged memory as source/destination, so looking forward to this. And preferably with no checks, since we would use this inside a loop where we are already sure we are within bounds. |
@nietras you may want to provide feedback on the api review dotnet/apireviews#23 as there is still some debate on the exact api surface |
After extension discussion we decided against these APIs. See notes for more details. This feature will be replaced by dotnet/corefx#5474. |
Summary
Currently, the System.Numerics.Vectors library provides no way to construct
Vector<T>
objects out of native pointers, or from anything other than an array or a single constant element. Although part of the draw of this library is access to high-performance vector math without resorting to native or unsafe code, there’s a lot of reasons you might have your data stored somewhere outside of a managed array. It’s not reasonable to copy all of your data into an array just to do vector operations on it, and then copy it back to its original location. Additionally, you might want to construct a one-off vector from the stack (perhaps usingstackalloc
), without having to make an entire array for the purpose of a single vector. Even if you have a safeIntPtr
to your storage location, you cannot currently construct aVector<T>
from it.We should expose better support for, at the very least, constructing
Vector<T>
’s from arbitrary pointers, and storing them back into arbitrary locations.Proposed API
Because all pointer types can be implicitly converted to a
void*
pointer, you can use the first overloads above to load aVector<float>
by passing afloat*
for example. It also allows you to load aVector<float>
with any other pointer type, but in my opinion this is not a problem. Even with the alternate approach below (see “Alternative Approaches”), you can simply cast a pointer type to another and construct any kind ofVector<T>
you would like. This is desirable in some situations if you want to perform different calculations or reinterpret your data in some way.Constructor
This behaves exactly the same way as the constructor taking an array, and allows an optional offset to be given in order to skip elements at the beginning of your storage.
CopyTo
This behaves exactly the same way as
CopyTo(T[])
, also with an optional offset to control the storage location.JIT Recognition
These new overloads must be recognized by the JIT. Functionally, they are pretty much identical to the existing overloads which operate on arrays.
Alternative Approach
Instead of a “universal” pointer constructor, we could expose some “factory” methods which take a specifically-typed pointer and return a specifically typed
Vector<T>
.I don’t personally favor this approach as using static methods for construction doesn’t necessarily mesh with the existing usage patterns, and it also just adds a lot more methods than constructors (two per primitive type, so 20 in total). Also, It would still make sense to add the
IntPtr
constructor even if we went this route.Related to https://github.com/dotnet/corefx/issues/5106, https://github.com/dotnet/corefx/issues/3741
I have part of the additions (
void*
overloads) implemented in this branch with tests added.Clarifications
Q: Since
Vector<T>
is a struct, why do we need special constructors for pointers pointers? Other BCL structures don't need special constructors for this, you can just cast, de-reference, etc.Because
Vector<T>
is a generic struct, you can't directly take it's address, nor have a pointer to it.Consider
Vector4
, which is sort of similar toVector<float>
. If you want to get aVector4
from afloat*
or vice-versa, you can just do the following, and you don't need a specialized constructor for it:Because
Vector<T>
is generic, the above mechanisms aren't applicable and you're much more limited in how you can constructor and store them with pointers.The text was updated successfully, but these errors were encountered: