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

Change OwnedMemory to MemoryManager and add an IMemoryOwner. #17340

Merged
merged 6 commits into from
Mar 30, 2018

Conversation

ahsonkhan
Copy link
Member

@ahsonkhan ahsonkhan commented Mar 30, 2018

  • IRetainable is IPinnable (Release is Unpin)
  • OwnedMemory is split into MemoryManager and IMemoryOwner
  • Reorder the arguments in the ctor of MemoryHandle
  • Add a static method CreateFromPinnedArray and use the high bit on length for pre-pinned arrays
  • Make the internal ctor that took OwnedMemory public (now takes MemoryManager) and add argument validation
  • Reset index, length if TryGetMemoryManager returns false (cc @benaadams)

Upcoming PR on the corefx side: dotnet/corefx#28640

cc @joshfree, @GrabYourPitchforks, @KrzysztofCwalina, @pakrym, @davidfowl, @jkotas, @stephentoub

cc @Anipik, @safern - many of these changes will need to be mirrored

@GrabYourPitchforks
Copy link
Member

GrabYourPitchforks commented Mar 30, 2018

Guidance & documentation should be coming online over the next few hours. I'll send links once I've finished the docs. These docs will also justify the API changes and should answer questions anybody might have regarding this.

@davidfowl
Copy link
Member

Is there any way this could be done in stages or nah?

@ahsonkhan
Copy link
Member Author

Is there any way this could be done in stages or nah?

We would have to only add APIs (leaving all the old APIs as is), wait for it to propagate, update all the calls, and then remove the old APIs. The propagation time across all repos is too large for us to wait for the round-trip. Also, this change is a bit more complex, so doing it in stages is a lot more difficult in general.

@joshfree
Copy link
Member

Is there any way this could be done in stages or nah?

Given the goal of having this done Friday, I think the best way is to get this merged tonight and get it properly consumed in corefx tomorrow AM.

/// <summary>
/// The number of items in the Memory<typeparamref name="T"/>.
/// </summary>
public virtual int Length => GetSpan().Length;
Copy link
Member

Choose a reason for hiding this comment

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

With this change; any reason for this to be virtual?

Copy link
Member

Choose a reason for hiding this comment

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

Probably to avoid the GetSpan() call?

Copy link
Member

Choose a reason for hiding this comment

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

Could have done

public int Length { get; }

protected MemoryManager(int length)
{
    Length = length;
}

public Memory<T> Memory => new Memory<T>(this, 0, Length);

Unless its valid that derived classes of MemoryManager can change in length on the go?

Copy link
Member

@GrabYourPitchforks GrabYourPitchforks Mar 30, 2018

Choose a reason for hiding this comment

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

@benaadams There are scenarios where this can change; e.g., if the factory pools and reuses these instances. We can provide a default implementation of this logic via reading the value from GetSpan(), but as a perf optimization implementations may want to provide the value in a more direct manner.

Copy link

Choose a reason for hiding this comment

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

Why would it change in case of a pool? We usually don't return exact size requested.

Copy link
Member

Choose a reason for hiding this comment

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

@benaadams @GrabYourPitchforks just made me aware of the fact that this type will rarely be used directly because the MemoryPool returns IMemoryOwner<T>, so really none of this matters. Though as an implementer of MemoryManager<T>, I think it's confusing to expose length. So lets try to come up with something concrete.

Copy link
Member

Choose a reason for hiding this comment

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

K so Memory won't inline due to the interface.

However as the implementer you will have to get a Length somewhere and possibly an Offset, so can stick it on the type and have everything else inline and have a good performance implementation with no additional virtuals or the need to implement the two methods?

public abstract class MemoryManager<T> : IMemoryOwner<T>, IPinnable
{
    protected int Offset { get; set; }
    protected int Length { get; set; }

    protected MemoryManager()
    {
    }
    
    protected MemoryManager(int length)
    {
        Length = length;
    }

    protected MemoryManager(int offset, int length)
    {
        Offset = offset;
        Length = length;
    }

    public Memory<T> Memory => new Memory<T>(this, Offset, Length);
    
    // Rest as now

Copy link
Member

@benaadams benaadams Mar 30, 2018

Choose a reason for hiding this comment

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

Or kill Length and force the implementation

public abstract class MemoryManager<T> : IMemoryOwner<T>, IPinnable
{
    public abstract Memory<T> Memory { get; }
    
    // Rest as now

Passing off the validation of offset and length to derived class

public class MyMemory : MemoryManager<byte>
{
    private int Offset { get; set; }
    private int Length { get; set; }
    
    public MyMemory(...)
    {
        // ...
    }
    
    public override Memory<T> Memory => new Memory<T>(this, Offset, Length);
} 

Copy link
Member

Choose a reason for hiding this comment

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

@GrabYourPitchforks making it abstract?

Copy link
Member Author

Choose a reason for hiding this comment

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

Thanks for the feedback. In the interest of time, I am going to merge the change as is and we can consider changing this separately.

@joshfree
Copy link
Member

@MattGal @mmitche is there a known issue with the macos ci queue? this job has been sitting at queue position 93 the past 3 hours - and now the queue depth is up to 124 and growing.

@GrabYourPitchforks
Copy link
Member

FYI, https://gist.github.com/GrabYourPitchforks/4c3e1935fd4d9fa2831dbfcab35dffc6 is the latest draft of the Memory<T> usage guidelines (detailing ownership, lifetime management, and expectations when you're consuming Memory<T> in your public APIs).

The document detailing Memory<T> vs. IMemoryOwner<T> vs. MemoryManager<T> is still being written.

@ahsonkhan
Copy link
Member Author

is there a known issue with the macos ci queue? this job has been sitting at queue position 93 the past 3 hours - and now the queue depth is up to 124 and growing.

OSX10.12 x64 Checked Innerloop Build and Test — Triggered. (93/134 on osx-10.12||OSX.1012.Amd6.Open)

@joshfree
Copy link
Member

@ahsonkhan don't block on the mac leg; https://github.com/dotnet/core-eng/issues/3064

@GrabYourPitchforks
Copy link
Member

And here's the doc detailing the relationship between Memory<T>, IMemoryOwner<T>, and MemoryManager<T>, complete with implementation guidance and samples.
https://gist.github.com/GrabYourPitchforks/8efb15abbd90bc5b128f64981766e834

@mmitche
Copy link
Member

mmitche commented Mar 30, 2018

@joshfree
Copy link
Member

@ahsonkhan you're free to merge this given the infra issue blocking the last 2 legs from actually executing and the minimal risk of a macos or arm specific issue in this PR.

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.

7 participants