-
Notifications
You must be signed in to change notification settings - Fork 4.9k
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
Move Span.DangerousCreate to MemoryMarshal.Create #24562
Comments
Just raising the question, do we need to have similar API for {ReadOnly}Memory? I mean public static class MemoryMarshal {
public static Memory<T> CreateMemory(object obj, ref T objectData, int length);
public static ReadOnlyMemory<T> CreateReadOnlyMemory(object obj, ref T objectData, int length);
} per the comment https://github.com/dotnet/corefx/issues/24296#issuecomment-341586055 on #23670 looks not preferred though. |
No, Memory is not capable of storing interior pointers/refs |
Sorry I meant something like public static class MemoryMarshal {
public static Memory<T> CreateMemory(T[] array, int start, int length);
public static ReadOnlyMemory<T> CreateReadOnlyMemory(T[] array, int start, int length);
} This is just will not validate parameter (similar to DangerousCreate). I don't think we need it, I am just want to be an explicit decision we don't need it. |
I think parameter validation might be an issue for Spans which are super optimized and the validation can add up. Memory APIs have enough meat in them that I think the validation overhead is not significant. But, if people complain and show such overhead, we could always add such APIs in the next release. |
I think we should kill the Create overload that takes first argument as Instead, we should have |
Also, we may want to rename |
Edit: Creating new issue instead |
@KrzysztofCwalina, if you agree, please update the original proposal to remove the first argument (object obj). |
I agree that the name is ugly and that it is an eyesore on MemoryExtensions/Span. So I like the rename to "Cast". I am not totally convinced the method needs to go to MemoryMarshal. The method is super useful and people will be using it no netter where it is and where it lives, especially that it is very much portable and safe in 99% of the cases. The gymnastics we go through to deswade people from using it will hurt 99% of the users and I doubt will help the 1%; they will have to discover the problems the hard way and change thir code. |
@ahsonkhan Opened dotnet/corefx#26368 on the Cast, so we may rather discuss it there. I agree that the Cast is useful, but you need to know what you are doing. I think there needs to be a small speed-bump to get to it to make it clear. |
For example, we had to take a pretty no-trivial servicing fix in .NET Core 2.0 for Windows ARM because the code played it loose with alignment: dotnet/corefx#25763 . |
I was referring to removing the object argument from the DangerousCreate methods in the proposed API and only have it be a netcoreapp specific API. |
I prefer the removal of the object argument if it isn't required, because it's not at all clear to me within a minute of searching what the purpose of the "object" parameter actually is. |
Ahson and I discussed this in person a bit more. Here's an updated proposal: Current API with usagecurrent API: public struct Span<T> {
public static Span<T> DangerousCreate(object obj, ref T objectData, int length);
}
public struct ReadOnlySpan<T> {
public static ReadOnlySpan<T> DangerousCreate(object obj, ref T objectData, int length);
} Usage: Person[] arr = new Person[] { new Person("Stan"), new Person("Alex"), new Person("Krzysztof") };
Person alex = arr[1];
Span<Person> spanAtAlex = Span<Person>.DangerousCreate(arr, alex, 2); // Span[Alex, Krzysztof] IssuesThe main issue right now with DangerousCreate is that the fast span and slow span have different implementations. Fast span doesn't need the first API Option 1 - leave the argument validation behavior as-is public static class MemoryMarshal {
// Slow versions of the below throw a nullref on a null obj parameter.
// We have the option of making the fast version throw something also, or just leaving it with no checks.
public static Span<T> CreateSpan(object obj, ref T objectData, int length);
public static ReadOnlySpan<T> CreateReadOnlySpan(object obj, ref T objectData, int length);
} Usage: Person[] arr = new Person[] { new Person("Stan"), new Person("Alex"), new Person("Krzysztof") };
Person alex = arr[1];
Span<Person> spanAtAlex = MemoryMarshal.CreateSpan<Person>(arr, alex, 2); // Span[Alex, Krzysztof] API Option 2 - give fast span its own API without the obj parameter // fast span
public static class MemoryMarshal {
public static Span<T> CreateSpan(ref T objectData, int length);
public static ReadOnlySpan<T> CreateReadOnlySpan(ref T objectData, int length);
}
// slow span - T as offset
public static class MemoryMarshal {
public static Span<T> CreateSpan(object obj, ref T objectData, int length);
public static ReadOnlySpan<T> CreateReadOnlySpan(object obj, ref T objectData, int length);
}
// slow (or both) spans - int as offset
public static class MemoryMarshal {
// use int as offset
public static Span<T> CreateSpan(object obj, int offset, int length);
public static ReadOnlySpan<T> CreateReadOnlySpan(object obj, int offset, int length);
} Usage: // slow span - T as offset
Person[] arr = new Person[] { new Person("Stan"), new Person("Alex"), new Person("Krzysztof") };
Person alex = arr[1];
Span<Person> spanAtAlex = MemoryMarshal.CreateSpan<Person>(arr, alex, 2); // Span[Alex, Krzysztof]
// slow span (or fast span) - int as offset
Person[] arr = new Person[] { new Person("Stan"), new Person("Alex"), new Person("Krzysztof") };
Span<Person> spanAtAlex = MemoryMarshal.CreateSpan<Person>(arr, 1, 2); // Span[Alex, Krzysztof]
// fast span
Person[] arr = new Person[] { new Person("Stan"), new Person("Alex"), new Person("Krzysztof") };
Person alex = arr[1];
Span<Person> spanAtAlex = MemoryMarshal.CreateSpan<Person>(alex, 2); // Span[Alex, Krzysztof] |
You example is slicing arrays. If you need to slice array, you can just use the current Span constructors that take arrays. DangerousCreate constructor was meant to create Span over fields of objects. The main problem with that is that it is never needed in practice. I have not seen a real world code that would actually do that. It is a very corner case scenarios. We should not be adding convoluted APIs that nobody will use. |
I think we should either delete this completely without replacement; or add the first set APIs from your option 2 for fast span only:
This API is constructor equivalent of I think one of the common uses of this API would be to let you create fixed size Spans of non-primitive types over stack allocated memory. For example:
|
I could only find two places where we use the DangerousCreate method, written as is. And in both cases, null is passed as the first argument: StringBuilder origStringBuilder = FormatCustomized(dateTime, ReadOnlySpan<char>.DangerousCreate(null, ref nextCharChar, 1), dtfi, offset, result); StringBuilder origStringBuilder = FormatCustomized(value, ReadOnlySpan<char>.DangerousCreate(null, ref nextCharChar, 1), dtfi, result); |
BTW: The existing Span constructor that takes |
@tarekgh, did we just recommend people use this API to customize span creation exceptions? If not, then I think we can remove this API (DangerousCreate(obj, T, int) |
I am not aware of any case we recommended that. would be good to check with aspnet if they already using it and what is their scenario. as @jkotas suggested there is a way to achieve the same result so I think we should be ok to remove this API. CC @davidfowl |
Here's an updated proposal: Current API public struct Span<T> {
public static Span<T> DangerousCreate(object obj, ref T objectData, int length);
}
public struct ReadOnlySpan<T> {
public static ReadOnlySpan<T> DangerousCreate(object obj, ref T objectData, int length);
} Proposed APIRemove the obj parameter and make it fast span specific public static class MemoryMarshal {
public static Span<T> CreateSpan(ref T reference , int length);
public static ReadOnlySpan<T> CreateReadOnlySpan(ref T reference , int length);
} |
Nit: Should the argument name be |
Yeah, that's better. Updated. |
We should consider changing the second overload to be |
Consume Span moves - [Move Span.DangerousCreate to MemoryMarshal.CreateSpan](#26139) - [Move ReadOnlySpan.DangerousCreate to MemoryMarshal.CreateReadOnlySpan](#26139) - [Move Span.NonPortableCast to MemoryMarshal.Cast](#26368) - [Move ReadOnlySpan.NonPortableCast to MemoryMarshal.Cast](#26368) - [Add ToString override to Span and ReadOnlySpan](#26295) - [Add ToEnumerable function to MemoryMarshal that takes a Memory](#24854)
@terrajobst When trying to use the API today, I was slightly suprised to discover that |
Yes @GoldenCrystal, we couldn't do |
Hi, I was actually using that particular API (see referenced issue above this post, or here for an example), as it was the only available API (as far as I know) that can be used to create Right now I can't upgrade from As pointed out in the other issue, since this specific API could technically still work on .NET Standard 2.0 (as it does work perfectly fine using version I've tried experimenting with Is there some other way around this using the available APIs? Thanks! 😄 |
I cannot think about nice way around this. You may be able to hack around it if you manually set the object and offset in the portable Span. |
@jkotas Yeah that's what I feared. I just wish I had seen this issue earlier though, I might have pointed out this particular use case back then to try to convince you guys to keep that I thought about manually setting the object and offset, but IIRC all those needed APIs/ctors are internal so I'd probably need to use reflections to do that, and that'd make the whole point of saving memory copies to gain speed pointless anyways. Thanks for your help though, I'll go ahead and refactor those calls out then. |
@Sergio0694 see my https://github.com/dotnet/corefx/issues/32943#issuecomment-475972385 on the If you know a way of circumventing this via reflection that works for both slow and fast Span I am interested. 😄 |
@nietras I've written a class that can solve the issue for you. Note that it will have a slight overhead due to the explicit pinning, but I've tried to make it as easy as possible to use. /// <summary>
/// A <see langword="class"/> that exposes <see cref="Span{T}"/> instances from a 2D <typeparamref name="T"/> array, on .NET Standard 2.0
/// </summary>
public sealed class Memory2D<T> : IDisposable where T : unmanaged
{
private readonly GCHandle Handle;
private readonly T[,] Array;
/// <summary>
/// Crates a new <see cref="Memory2D{T}"/> instance that wraps a given 2D <typeparamref name="T"/> array
/// </summary>
/// <param name="array">The 2D <typeparamref name="T"/> array to wrap</param>
public Memory2D(T[,] array)
{
Array = array;
Handle = GCHandle.Alloc(array, GCHandleType.Pinned);
}
/// <summary>
/// Gets a <see cref="Span{T}"/> that targets a specified row from the wrapped array
/// </summary>
/// <param name="row">The target row to retrieve</param>
public unsafe Span<T> this[int row] => new Span<T>(Unsafe.AsPointer(ref Array[row, 0]), Array.GetLength(1));
/// <summary>
/// Gets a <see cref="Span{T}"/> that targets a specified row interval in the wrapped array
/// </summary>
/// <param name="start">The starting row</param>
/// <param name="end">The ending row</param>
public unsafe Span<T> GetRows(int start, int end) => new Span<T>(Unsafe.AsPointer(ref Array[start, 0]), (end - start) * Array.GetLength(1));
/// <summary>
/// Implicitly creates a <see cref="Memory2D{T}"/> instance from an input 2D <typeparamref name="T"/> array
/// </summary>
/// <param name="array">The 2D <typeparamref name="T"/> array to wrap</param>
public static implicit operator Memory2D<T>(T[,] array) => new Memory2D<T>(array);
/// <inheritdoc/>
public void Dispose() => Handle.Free();
} And you can use it like this: public static void Test<T>(T[,] array) where T : unmanaged
{
using (Memory2D<T> memory = array)
{
Span<T> row = memory[0];
// Do things with the row span...
Span<T> rows = memory.GetRows(0, 2);
// Do things with the rows span...
}
} Hope this works for you! 😄 NOTE: since this @jkotas I'll leave this here for future reference in case it might help others in the same situation too. I guess a solution like this would be preferable to trying to hack into the |
@Sergio0694 that is not really what I was looking for or asking for 😄 What you are showing is basically just native memory (by pinning), which Span of course can be created over. That is not the problem here. So no we cannot create Spans over managed memory that is not pinned, which is the problem. So the fact we can't do that forces devs to apply pinning which is bad in my view. A sympton of working around an API instead of an API working for you. |
@nietras Well yeah, I thought we had already established that there was no way of just using a reference-based I mean, the purpose of that code was to show a possible way (albeit pin-based, so not ideal of course) to get a Unless the CoreFX guys decide to make the |
@Sergio0694 no problem. 😄 It is in fact your hack I would be interested to see? 👍 How can you do this via reflections? There are quite a few restrictions around |
I am just curious that how come this method is allowed outside of the unsafe context? So now one can crash the runtime easily without using unsafe. I am sure people here have good reasons. I just wanted to learn. |
current API:
proposed API:
The text was updated successfully, but these errors were encountered: