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

Add GetReference and TryGetArray to MemoryMarshal #15417

Merged
merged 4 commits into from
Dec 8, 2017

Conversation

ahsonkhan
Copy link

@ahsonkhan ahsonkhan commented Dec 7, 2017

Sets up the ground work for:
https://github.com/dotnet/corefx/issues/25412
https://github.com/dotnet/corefx/issues/25615

Test updates and changes in corefx to follow after. dotnet/corefx#25789

Following the staging plan from here: https://github.com/dotnet/corefx/issues/23881#issuecomment-343767740

  1. Add MemoryExtensions.GetReference/TryGetArray
  2. Convert all uses of DangerousGetPinnableReference/DangerousTryGetArray in coreclr, corefx, corert, corefxlab, aspnet, ... to MemoryExtensions.GetReference
  3. Change DangerousGetPinnableReference to whatever we like to make it fit the pinning pattern and remove DangerousTryGetArray.

Doing it this way will avoid the need for complex staging or things being on the floor for extensive periods of time.

cc @jkotas, @stephentoub, @KrzysztofCwalina

@@ -409,6 +409,9 @@ public T[] ToArray()
/// </summary>
public static Span<T> Empty => default(Span<T>);

// This exposes the internal representation for Span-related apis use only.
internal ByReference<T> Pointer => _pointer;
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 return ref T. ByReference<T> should be used only for the fields.

Copy link
Author

Choose a reason for hiding this comment

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

Why is that?

Wouldn't this make the implementation, essentially a duplicate, but internal, version of DangerousGetPinnableReference?

i.e.

internal ref T Reference => ref _pointer.Value;

Do we then just return Reference from MemoryMarshal.GetReference()?

@@ -23,5 +24,32 @@ public static class MemoryMarshal
/// </remarks>
public static Memory<T> AsMemory<T>(ReadOnlyMemory<T> readOnlyMemory) =>
Unsafe.As<ReadOnlyMemory<T>, Memory<T>>(ref readOnlyMemory);

[MethodImpl(MethodImplOptions.AggressiveInlining)]
Copy link
Member

@jkotas jkotas Dec 7, 2017

Choose a reason for hiding this comment

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

Why is the AggresiveInlining necessary?

I know that you got into habit of marking everything with AggresiveInlining. I do not think it is a good idea to do blindly - it should be done only when you have a clear data that it helps.

Copy link
Author

Choose a reason for hiding this comment

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

For this instance, I wanted to remain consistent with DangerousGetPinnableReference which already had it, presumable for good reason. I will try to run some experiments.

Copy link
Member

Choose a reason for hiding this comment

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

ok

Copy link
Author

Choose a reason for hiding this comment

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

Inlining in almost all cases made no difference.

The only case inlining helps is if the implementation is using in and the Reference property.

@@ -23,5 +24,32 @@ public static class MemoryMarshal
/// </remarks>
public static Memory<T> AsMemory<T>(ReadOnlyMemory<T> readOnlyMemory) =>
Unsafe.As<ReadOnlyMemory<T>, Memory<T>>(ref readOnlyMemory);

[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static ref T GetReference<T>(in Span<T> span) => ref span.Reference;
Copy link
Member

Choose a reason for hiding this comment

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

Have you tried whether it is worth it to have in here? (It was discussed in the issue.)

Copy link
Author

Choose a reason for hiding this comment

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

Using in doesn't really help (at least for fast span). And if we don't inline the method, it actually hurts performance.

If I pass a Span using in, it seems to hurt performance in some cases:

[MethodImpl(MethodImplOptions.NoInlining)]
public static void CallTestMethodWithIn(Span<int> span)
{
    int temp = TestMethodWithIn(span);
}

[MethodImpl(MethodImplOptions.NoInlining)]
public static void CallTestMethodWithoutIn(Span<int> span)
{
    int temp = TestMethodWithoutIn(span);
}

[MethodImpl(MethodImplOptions.NoInlining)]
public static int TestMethodWithIn(in ReadOnlySpan<int> span)
{
    int val = 0;
    for(int i = 0; i < span.Length; i++)
        val ^= span[i];
    return val;
}

[MethodImpl(MethodImplOptions.NoInlining)]
public static int TestMethodWithoutIn(ReadOnlySpan<int> span)
{
    int val = 0;
    for (int i = 0; i < span.Length; i++)
        val ^= span[i];
    return val;
}

Runtime (s):
CallTestMethodWithoutIn: 6.3821768
CallTestMethodWithIn: 6.8055258

There is an extra compare and jump in the loop body if I pass the Span with in.
image

public static ref T GetReference<T>(in Span<T> span) => ref span.Reference;

[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static ref readonly T GetReference<T>(in ReadOnlySpan<T> span) => ref span.Reference;
Copy link
Member

Choose a reason for hiding this comment

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

I do not think the Reference property will get inlined here for the class case. We may want to make the field internal instead.

Copy link
Author

Choose a reason for hiding this comment

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

The ByReference<T> _pointer field?

Copy link
Member

Choose a reason for hiding this comment

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

Yes.

Copy link
Author

Choose a reason for hiding this comment

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

Yes, it looks like without AggressiveInlining it doesn't get inlined.

object obj = readOnlyMemory.GetObjectStartLength(out int index, out int length);
if (index < 0)
{
if (((OwnedMemory<T>)obj).TryGetArray(out var segment))
Copy link
Member

Choose a reason for hiding this comment

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

Can this be just return (((OwnedMemory<T>)obj).TryGetArray(out var arraySegment) ?

Copy link
Member

Choose a reason for hiding this comment

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

I see - you need to add the local index.

@ahsonkhan
Copy link
Author

I tried to toggle the implementation based on these 3 factors:

  • AggressiveInlining
  • Use of in
  • Use Reference property or _pointer field

Most of these had negligible impact. The only one that made a difference was AggressiveInlining only if I passed the Span using in.
image

Given that these factors don't really affect the results (and using in requires AggressiveInlining), I will use this implementation:

GetReference(Span<T> span) => ref span._pointer.Value;

@jkotas jkotas merged commit 0919eb3 into dotnet:master Dec 8, 2017
@ahsonkhan ahsonkhan deleted the AddDangerousAPIs branch December 9, 2017 00:17
dotnet-bot pushed a commit to dotnet/corert that referenced this pull request Dec 9, 2017
* Add GetReference and TryGetArray to MemoryMarshal

* Marking GetReference with AggressiveInlining

* Do not use ByReference as a return type.

* Addressing PR feedback.

Signed-off-by: dotnet-bot <dotnet-bot@microsoft.com>
jkotas pushed a commit to dotnet/corert that referenced this pull request Dec 9, 2017
* Add GetReference and TryGetArray to MemoryMarshal

* Marking GetReference with AggressiveInlining

* Do not use ByReference as a return type.

* Addressing PR feedback.

Signed-off-by: dotnet-bot <dotnet-bot@microsoft.com>
jashook pushed a commit to jashook/coreclr that referenced this pull request Dec 12, 2017
* Add GetReference and TryGetArray to MemoryMarshal

* Marking GetReference with AggressiveInlining

* Do not use ByReference as a return type.

* Addressing PR feedback.
dotnet-bot pushed a commit to dotnet/corefx that referenced this pull request Jan 13, 2018
* Add GetReference and TryGetArray to MemoryMarshal

* Marking GetReference with AggressiveInlining

* Do not use ByReference as a return type.

* Addressing PR feedback.

Signed-off-by: dotnet-bot-corefx-mirror <dotnet-bot@microsoft.com>
dotnet-bot pushed a commit to dotnet/corefx that referenced this pull request Jan 13, 2018
* Add GetReference and TryGetArray to MemoryMarshal

* Marking GetReference with AggressiveInlining

* Do not use ByReference as a return type.

* Addressing PR feedback.

Signed-off-by: dotnet-bot-corefx-mirror <dotnet-bot@microsoft.com>
safern pushed a commit to dotnet/corefx that referenced this pull request Jan 16, 2018
* Add GetReference and TryGetArray to MemoryMarshal

* Marking GetReference with AggressiveInlining

* Do not use ByReference as a return type.

* Addressing PR feedback.

Signed-off-by: dotnet-bot-corefx-mirror <dotnet-bot@microsoft.com>
safern pushed a commit to dotnet/corefx that referenced this pull request Jan 16, 2018
* Add GetReference and TryGetArray to MemoryMarshal

* Marking GetReference with AggressiveInlining

* Do not use ByReference as a return type.

* Addressing PR feedback.

Signed-off-by: dotnet-bot-corefx-mirror <dotnet-bot@microsoft.com>
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.

None yet

2 participants