Skip to content
This repository has been archived by the owner on Jan 23, 2023. It is now read-only.

Misc Span/Memory changes #15941

Merged
merged 2 commits into from
Jan 23, 2018
Merged

Misc Span/Memory changes #15941

merged 2 commits into from
Jan 23, 2018

Conversation

@ianhays ianhays added this to the 2.1.0 milestone Jan 20, 2018
@ianhays ianhays self-assigned this Jan 20, 2018
@ianhays ianhays requested review from ahsonkhan and jkotas January 20, 2018 00:09
/// <summary>
/// Returns a <see cref="String"/> with the name of the type and the number of elements
/// </summary>
/// <returns>A <see cref="String"/> with the name of the type and the number of elements</returns>
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: Is the returns xml tag necessary? It seems redundant with whatever has been stated in the summary anyway.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's definitely redundant. I added it because I thought that intellisense may do something useful with the returns tag, but I couldn't actually find a case where it's used without the summary. I'll remove it.

/// Thrown if the Length property of the new Span would exceed Int32.MaxValue.
/// </exception>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static ReadOnlySpan<TTo> Cast<TFrom, TTo>(ReadOnlySpan<TFrom> source)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This looks like a duplicate.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks, this should be casting Span

/// Thrown if the Length property of the new Span would exceed Int32.MaxValue.
/// </exception>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static ReadOnlySpan<TTo> Cast<TFrom, TTo>(ReadOnlySpan<TFrom> source)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same here. This looks like a duplicate.

Copy link
Member

@ahsonkhan ahsonkhan left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Otherwise, LGTM.

ThrowHelper.ThrowInvalidTypeWithPointersNotSupported(typeof(TTo));

return new ReadOnlySpan<TTo>(
ref Unsafe.As<TFrom, TTo>(ref MemoryMarshal.GetReference(source)),
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This can use _pointer.Value directly. No need to go through GetReference wrapper.

@jkotas
Copy link
Member

jkotas commented Jan 20, 2018

It was not clear to me from the discussions that we are adding MemoryMarshal.ToEnumerable (I thought we have abandoned it as perf trap) and Memory.Cast (I thought that we will add TryCast instead). @KrzysztofCwalina ?

Otherwise LGTM - once you make the CI green.

@ahsonkhan
Copy link
Member

ahsonkhan commented Jan 20, 2018

and Memory.Cast (I thought that we will add TryCast instead).

From our API review today: [starts at 13:30] https://youtu.be/Qcqn-yv1X0o?t=13m30s
We decided to add MemoryMarshal.Cast to enable cases where the user explicitly aligns first (for example, Span.IndexOf), and then wants to avoid the unnecessary checks that TryCast would do. Hence, we decided to keep Cast (on MemoryMarshal) and add a TryCast extension method, for the common case (with higher visibility).

/// <param name="length">The number of <typeparamref name="T"/> elements the memory contains.</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
[EditorBrowsable(EditorBrowsableState.Never)]
public static ReadOnlySpan<T> DangerousCreate(object obj, ref T objectData, int length) => new ReadOnlySpan<T>(ref objectData, length);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

At least from my experience, it's easier to stage changes like this across coreclr/corefx if when changing a method's name/location, you first add the new method in coreclr, let that propagate to corefx and fix everything up, and then go back and delete the old method. Otherwise, there's a period of time where coreclr and corefx don't work together, and if there's any other issue happening at the same time, you may not be able to fix things quickly.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Great idea! I wasn't too pleased with the broken in-between phase. I'll do that.

/// Returns a <see cref="String"/> with the name of the type and the number of elements
/// </summary>
/// <returns>A <see cref="String"/> with the name of the type and the number of elements</returns>
public override string ToString() => $"System.Span[{Length}]";
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Performance here isn't important, but FYI this is more expensive than just:

"System.Span[" + Length.ToString() + "]";

The string interpolation version will box Length, will create an array to store that boxed length, and then will end up calling string.Format, whereas the above allocates the string for length and then calls String.Concat(string, string, string).

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good to know, thanks :)

int newLength = checked((int)((long)source.Length * Unsafe.SizeOf<TFrom>() / Unsafe.SizeOf<TTo>()));
return new ReadOnlySpan<TTo>(Unsafe.As<Pinnable<TTo>>(source.Pinnable), source.ByteOffset, newLength);
}

#else
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why do we have both the portable and fast versions in the same file? Can we split this into separate files like we do elsewhere?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sure. I'll have to temporarily have four files to prevent corefx from breaking before the accompanying PR there is merged:

  • MemoryMarshal.Fast.cs
  • MemoryMarshal.Portable.cs
  • MemoryMarshal.Shared.cs (only the few functions that are the same for both fast/portable)
  • MemoryMarshal.cs (old file without changes)

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why do you need to have MemoryMarshal.Shared.cs file? I think these can be just be added to the existing MemoryMarshal.cs file.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The purpose of the Shared file is to make it so that merging the coreclr PR won't cause CoreFX to break. To do that, we need to mostly leave the existing MemoryMarshal file intact, which means we can't include it in coreclr if we also have a MemoryMarshal.Fast that contains some of the signatures that are currently living in MemoryMarshal.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There are two cases:

  • You are adding new APIs (including adding overloads of existing APIs with new signatures). In this case, you can just add it to MemoryMarshal.cs
  • You add modifying existing APIs. In this case, you will need to augment the mirror PR in CoreFX to get it through. MemoryMarshal.Shared.cs file is not going to help you with that.

What I am missing?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You add modifying existing APIs. In this case, you will need to augment the mirror PR in CoreFX to get it through. MemoryMarshal.Shared.cs file is not going to help you with that.

The way I have it currently set up, I'm not actually modifying any existing APIs, I'm only adding new API, so the mirror PR won't be broken.

You are adding new APIs (including adding overloads of existing APIs with new signatures). In this case, you can just add it to MemoryMarshal.cs

If I add the new API to MemoryMarshal, then coreclr needs to include MemoryMarshal, which means I'll need to remove the Fast-Specific section from MemoryMarshal to avoid conflicts with MemoryMarshal.Fast.cs, which means that the mirror PR to corefx will be broken for the non-portable builds unless I modify the mirror PR to include the MemoryMarshal.Fast file.

I was trying to avoid having to make fixes to the mirror PR, but if you're strongly against the Shared suffix, then I can. I'll just fix the mirror PR to include the new MM.Fast and MM.Portable files to get the build green then keep my feature CoreFX PR the same minus the new file inclusions.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

you're strongly against the Shared suffix

Yes, I am strongly against adding temporary files with .Shared suffix. It just means more round trips between repos to clean things up.

mirror PR to corefx will be broken for the non-portable builds

We should expect that you need to patch up the mirror PR for changes like these.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍 Shared removed.

/// <typeparam name="T">The element type of the <paramref name="memory" />.</typeparam>
/// <param name="memory">The Memory to view as an <see cref="IEnumerable{T}"/></param>
/// <returns>An <see cref="IEnumerable{T}"/> view of the given <paramref name="memory" /></returns>
public static IEnumerable<T> ToEnumerable<T>(Memory<T> memory)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Does this end up having the desired optimization? I would hope so, as Environment.CurrentManagedThreadId is available to it, but the Environment it has access to is also internal, so we should verify that's not throwing off the compiler.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@stephentoub Environment.CurrentManagedThreadId is not used here. Was this meant for something else?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's used by the compiler's implementation of iterators, and the compiler keys off its presence to determine whether to take the optimization that reuses the enumerable as the enumerator.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

(I expect it's fine though, as corelib already has other iterators in it, and they look to compile "correctly".)

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I checked the IL and confirmed that it is getting compiled correctly with the use of System.Environment::get_CurrentManagedThreadId()

/// <typeparam name="T">The element type of the <paramref name="memory" />.</typeparam>
/// <param name="memory">The Memory to view as an <see cref="IEnumerable{T}"/></param>
/// <returns>An <see cref="IEnumerable{T}"/> view of the given <paramref name="memory" /></returns>
public static IEnumerable<T> ToEnumerable<T>(Memory<T> memory)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should this be for ReadOnlyMemory<T> instead of for Memory<T>?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Seems like it might as well be, yeah.

- Add MemoryMarshal.CreateSpan to replace DangerousCreate
- Add MemoryMarshal.CreateReadOnlySpan to replace DangerousCreate
- Add MemoryMarshal.Cast to replace NonPortableCast
- Add ToString override to Span and ReadOnlySpan
- Add ToEnumerable function to MemoryMarshal that takes a ReadOnlyMemory
@ianhays
Copy link
Author

ianhays commented Jan 22, 2018

Thanks everyone for the feedback. I force pushed a new commit that addresses of all it and also leaves the existing functions in place to ease the intermediate stage per @stephentoub 's suggestion.

@@ -435,7 +435,8 @@
<Compile Include="$(MSBuildThisFileDirectory)System\Runtime\InteropServices\LayoutKind.cs" />
<Compile Include="$(MSBuildThisFileDirectory)System\Runtime\InteropServices\MarshalAsAttribute.cs" />
<Compile Include="$(MSBuildThisFileDirectory)System\Runtime\InteropServices\MarshalDirectiveException.cs" />
<Compile Include="$(MSBuildThisFileDirectory)System\Runtime\InteropServices\MemoryMarshal.cs" />
<Compile Include="$(MSBuildThisFileDirectory)System\Runtime\InteropServices\MemoryMarshal.Fast.cs" />
<Compile Include="$(MSBuildThisFileDirectory)System\Runtime\InteropServices\MemoryMarshal.Shared.cs" />
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This should stay MemoryMarshal.cs to follow our naming conventions.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done.

// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.

Copy link
Member

@jkotas jkotas Jan 22, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

MemoryMarshal.Portable.cs does not need to be in the shared CoreLib subtree. It can stay in the CoreFX-specific subtree.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good point, removed. I'll add it back to CoreFX in the System.Memory project.

Copy link
Member

@jkotas jkotas left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM. Thanks!

@jkotas jkotas merged commit c69e814 into dotnet:master Jan 23, 2018
dotnet-bot pushed a commit to dotnet/corert that referenced this pull request Jan 23, 2018
* Misc Span/Memory additions
- Add MemoryMarshal.CreateSpan to replace DangerousCreate
- Add MemoryMarshal.CreateReadOnlySpan to replace DangerousCreate
- Add MemoryMarshal.Cast to replace NonPortableCast
- Add ToString override to Span and ReadOnlySpan
- Add ToEnumerable function to MemoryMarshal that takes a ReadOnlyMemory

Signed-off-by: dotnet-bot <[email protected]>
dotnet-bot pushed a commit to dotnet/corefx that referenced this pull request Jan 23, 2018
* Misc Span/Memory additions
- Add MemoryMarshal.CreateSpan to replace DangerousCreate
- Add MemoryMarshal.CreateReadOnlySpan to replace DangerousCreate
- Add MemoryMarshal.Cast to replace NonPortableCast
- Add ToString override to Span and ReadOnlySpan
- Add ToEnumerable function to MemoryMarshal that takes a ReadOnlyMemory

Signed-off-by: dotnet-bot-corefx-mirror <[email protected]>
jkotas pushed a commit to dotnet/corert that referenced this pull request Jan 23, 2018
* Misc Span/Memory additions
- Add MemoryMarshal.CreateSpan to replace DangerousCreate
- Add MemoryMarshal.CreateReadOnlySpan to replace DangerousCreate
- Add MemoryMarshal.Cast to replace NonPortableCast
- Add ToString override to Span and ReadOnlySpan
- Add ToEnumerable function to MemoryMarshal that takes a ReadOnlyMemory

Signed-off-by: dotnet-bot <[email protected]>
jkotas pushed a commit to dotnet/corefx that referenced this pull request Jan 23, 2018
* Misc Span/Memory additions
- Add MemoryMarshal.CreateSpan to replace DangerousCreate
- Add MemoryMarshal.CreateReadOnlySpan to replace DangerousCreate
- Add MemoryMarshal.Cast to replace NonPortableCast
- Add ToString override to Span and ReadOnlySpan
- Add ToEnumerable function to MemoryMarshal that takes a ReadOnlyMemory

Signed-off-by: dotnet-bot-corefx-mirror <[email protected]>
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants