Skip to content

Conversation

@Sergio0694
Copy link
Member

Prerequisites

  • I have written a descriptive pull-request title
  • I have verified that there are no overlapping pull-requests open
  • I have verified that I am following matches the existing coding patterns and practice as demonstrated in the repository. These follow strict Stylecop rules 👮.
  • I have provided test coverage for my change (where applicable)

Description

This PR introduces some speed/memory improvements to the IMemoryGroup<T> interface and the various classes implementing it. In particular, I've tweaked a bit the existing enumerators, and I've added a new value-type MemoryGroupEnumerator<T> type that can be used through the C# 8 pattern-based interfaces for ref struct feature (see here). This change makes it so that whenever you use foreach on an IMemoryGroup<T> instance, the compiler will pick the new method over the IEnumerable<Memory<T>>.GetEnumerator() one, which lets us have exactly the same functionality as before (no changes at all needed in the rest of the codebase), but faster and without memory allocations like before. Pinging both James and Anton for review as this PR involves both the API surface as well as the actual performance of the IMemoryGroup<T> types. 😄🚀

@codecov
Copy link

codecov bot commented Apr 13, 2020

Codecov Report

Merging #1173 into master will decrease coverage by 0.00%.
The diff coverage is 62.06%.

Impacted file tree graph

@@            Coverage Diff             @@
##           master    #1173      +/-   ##
==========================================
- Coverage   82.47%   82.47%   -0.01%     
==========================================
  Files         687      688       +1     
  Lines       29871    29892      +21     
  Branches     3378     3378              
==========================================
+ Hits        24637    24654      +17     
- Misses       4534     4538       +4     
  Partials      700      700              
Flag Coverage Δ
#unittests 82.47% <62.06%> (-0.01%) ⬇️
Impacted Files Coverage Δ
...emory/DiscontiguousBuffers/MemoryGroup{T}.Owned.cs 92.85% <25.00%> (-2.15%) ⬇️
...ry/DiscontiguousBuffers/MemoryGroup{T}.Consumed.cs 80.00% <33.33%> (+7.27%) ⬆️
.../Memory/DiscontiguousBuffers/MemoryGroupView{T}.cs 83.33% <50.00%> (+0.47%) ⬆️
...harp/Memory/DiscontiguousBuffers/MemoryGroup{T}.cs 77.41% <50.00%> (+0.37%) ⬆️
...y/DiscontiguousBuffers/MemoryGroupEnumerator{T}.cs 77.77% <77.77%> (ø)

Continue to review full report at Codecov.

Legend - Click here to learn more
Δ = absolute <relative> (impact), ø = not affected, ? = missing data
Powered by Codecov. Last update eb312e0...9823eed. Read the comment docs.

@JimBobSquarePants
Copy link
Member

@Sergio0694 Looks good to me, I'd like @antonfirsov to review this though to be sure.

@Sergio0694
Copy link
Member Author

@JimBobSquarePants No worries (there's no rush), glad you like it though! 😄
I thought you'd appreciate this new trick shahahah

Also yeah absolutely, in fact I had already signed up @antonfirsov for a review, felt appropriate considering he was the original author of the memory group types, plus this PR is small but arguably a bit weird, so having more people double checking it doesn't hurt! 👍

@antonfirsov
Copy link
Member

Ok, will do it in the weekend.

Copy link
Member

@antonfirsov antonfirsov left a comment

Choose a reason for hiding this comment

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

Minor questions, otherwise LGTM.

/// </summary>
/// <typeparam name="T">The element type.</typeparam>
[EditorBrowsable(EditorBrowsableState.Never)]
public ref struct MemoryGroupEnumerator<T>
Copy link
Member

Choose a reason for hiding this comment

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

Technically, this doesn't require to be a ref struct (all members are heapable). Have you added the constraint to ensure it's only used in method locals?

Copy link
Member Author

@Sergio0694 Sergio0694 Apr 19, 2020

Choose a reason for hiding this comment

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

Yeah, it was mostly a precaution to prevent users from doing stuff like storing the iterator as a field in a class for later use, or other weird things. This type should really only ever be used implicitly by the compiler when using foreach, it should be completely transparent to users. Same reason for that EditorBrowsableState.Never as well 😄

public override int Count => this.source.Length;
public override int Count
{
[MethodImpl(InliningOptions.ShortMethod)]
Copy link
Member

Choose a reason for hiding this comment

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

The type is not sealed. Will Count be ever inlined actually?

Copy link
Member Author

Choose a reason for hiding this comment

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

I've added that because those methods are being called directly from the enumerator:

In theory (I hope), the JIT should be able to devirtualize and then inline that call, since all APIs are internal, so I assume it could potentially know the type is definitely just the one declared from there as a parameter. Of course, we could also declare those types as sealed, to be honest I'm not even sure why they're not already? 🤔

Copy link
Member

Choose a reason for hiding this comment

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

I'm unsure about JIT here, anyways this is a question of low importance.

Copy link
Member Author

Choose a reason for hiding this comment

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

Better one AggressiveInlining in excess than lacking one 🤣

@JimBobSquarePants JimBobSquarePants merged commit 8d404d6 into master Apr 20, 2020
@JimBobSquarePants JimBobSquarePants deleted the optimizations/memory-group-enumeration branch April 20, 2020 00:05
@Sergio0694
Copy link
Member Author

Yay for even less allocations! 🎉🚀

@JimBobSquarePants We might also want to follow up on my comment here and consider making those sub-types sealed as well, as it could potentially help out the JIT devirtualize some more calls.

@JimBobSquarePants
Copy link
Member

@Sergio0694 Yeah, as long as there's no reason we can think of that we would inherit from them @antonfirsov ?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants