-
Notifications
You must be signed in to change notification settings - Fork 1.4k
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
2D System.Memory-like primitives (Span2D<T>, Memory2D<T>) #3353
2D System.Memory-like primitives (Span2D<T>, Memory2D<T>) #3353
Conversation
Thanks Sergio0694 for opening a Pull Request! The reviewers will test the PR and highlight if there is any conflict or changes required. If the PR is approved we will proceed to merge the pull request 🙌 |
Microsoft.Toolkit.HighPerformance/Enumerables/ReadOnlyRefEnumerable{T}.cs
Show resolved
Hide resolved
Microsoft.Toolkit.HighPerformance/Extensions/ArrayExtensions.1D.cs
Outdated
Show resolved
Hide resolved
Microsoft.Toolkit.HighPerformance/Helpers/ParallelHelper.ForEach.IInAction2D.cs
Outdated
Show resolved
Hide resolved
Microsoft.Toolkit.HighPerformance/Helpers/ParallelHelper.ForEach.IInAction2D.cs
Outdated
Show resolved
Hide resolved
Microsoft.Toolkit.HighPerformance/Helpers/ParallelHelper.ForEach.IInAction2D.cs
Outdated
Show resolved
Hide resolved
Microsoft.Toolkit.HighPerformance/Helpers/ParallelHelper.ForEach.IInAction2D.cs
Show resolved
Hide resolved
Microsoft.Toolkit.HighPerformance/Helpers/ParallelHelper.ForEach.IRefAction2D.cs
Outdated
Show resolved
Hide resolved
This PR has been marked as "needs attention 👋" and awaiting a response from the team. |
This reverts commit 78a0266. Done to avoid conflicts, since these two enumerable types are already being overhauled in CommunityToolkit#3353 anyway.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Going to wait until #3351 gets merged.
Microsoft.Toolkit.HighPerformance/Enumerables/ReadOnlySpanEnumerable{T}.cs
Outdated
Show resolved
Hide resolved
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I looked at most of this code, and while I'm not 100% sure, I think it all looks good to me.
I've got 2 usability issues though:
- why are the width/height indexes called
i
,j
? why notx
,y
? - Why do Equals & GetHashCode just throw?
Hey @HurricanKai, thanks for the review! 😊 As for your questions:
|
@Sergio0694 this has some conflicts to resolve now? |
@michael-hawker Yup, was just waiting on #3378 to be merged first since that'll edit some of the same files anyway, so that here I'll just have to resolve conflicts once when pulling changes from |
@Sergio0694 #3378 is merged now, can you resolve conflicts and clean-up this PR so we can re-review and get it merged as well? Thanks! 🙂 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Few small comments on the unit tests, looking good!
@@ -23,12 +23,7 @@ public static class HashCodeExtensions | |||
/// <param name="span">The input <see cref="ReadOnlySpan{T}"/> instance.</param> | |||
[MethodImpl(MethodImplOptions.AggressiveInlining)] | |||
public static void Add<T>(ref this HashCode hashCode, ReadOnlySpan<T> span) | |||
#if SPAN_RUNTIME_SUPPORT |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This has changed now I guess from the runtime side?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I've removed that restriction because I've now added polyfills for the RuntimeHelpers.IsReferenceOrContainsReferences<T>()
API on .NET Standard 2.0 and below (I needed this for other code in the Memory2D<T>
and Span2D<T>
types, so I used it here too while I was at it), I use that here:
Now that older targets can test this too at runtime, we no longer have to add that extra type constraint, which was basically just a precaution due to the lack of this API at runtime. You can see how we switch the polyfill on here:
So now on all targets the API will just automatically pick the right implementation for each input type T
😄
|
||
Assert.ThrowsException<ArgumentOutOfRangeException>(() => array.GetColumn(-1)); | ||
|
||
Assert.ThrowsException<ArgumentOutOfRangeException>(() => array.GetColumn(20)); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We should also be testing the 'off-by-1' index of '3', eh? (here and where-ever)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Right! I had added those tests when getting rows/columns from spans, but not from 2D arrays directly!
Fixed in 7a65a29, for both GetRow
and GetColumn
in this file 😄
|
||
// Try to copy to a valid row and an invalid column (too short for the source span) | ||
bool shouldBeTrue = values.TryCopyTo(array1.GetRow(2)); | ||
bool shouldBeFalse = values.TryCopyTo(array1.GetColumn(1)); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We're not checking here that no mutation occurred, eh? As the line 281 above would have copied the same values? (I mean we know it failed as '20' is still in the 3rd row, but may be better to use a different column like '3' here to be safe to guard against this case too.)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Good catch! Also added one extra check for the entire target array for good measure 😄
Done in 6bb9e5a, including the same changes for the comment below.
UnitTests/UnitTests.HighPerformance.Shared/Extensions/Test_SpanExtensions.cs
Outdated
Show resolved
Hide resolved
@@ -135,8 +135,7 @@ public static partial class ParallelHelper | |||
/// Processes the batch of actions at a specified index | |||
/// </summary> | |||
/// <param name="i">The index of the batch to process</param> | |||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Expected for the inlining to disappear?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah this was just a leftover, it's not there in the other implementations either. This method is just called once per thread anyway and does all the job. It's the actual delegates within each invocation of this method that need to be inlined, as they're invoked in a tight loop, not this one. As in, this loop here:
// of CPU cores (which is an int), and the height of each batch is necessarily | ||
// smaller than or equal than int.MaxValue, as it can't be greater than the | ||
// number of total batches, which again is capped at the number of CPU cores. | ||
nint |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
So if this is native to the platform, how do we unit test the code for both possible sizes?
@@ -2,7 +2,7 @@ | |||
|
|||
<PropertyGroup> | |||
<TargetFrameworks>netstandard1.4;netstandard2.0;netstandard2.1;netcoreapp2.1;netcoreapp3.1</TargetFrameworks> | |||
<LangVersion>8.0</LangVersion> | |||
<LangVersion>9.0</LangVersion> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Does this need anything new for our build pipeline/tooling requirements for folks?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Commenting here about the same comments from the stream, for future reference. Right now this works on the latest versions of VS stable (not the preview preview), because it uses the same Roslyn codebase as VS Preview, just from an older branch. As such, some C# 9 features are buggy/outdated (eg. function pointers), but since here we're really only using the new nint type, that bit in particular mostly works. I only had to add an extra explicit cast here to ensure the correct codegen due to a small compiler bug that's fixed in VS Preview, for instance:
As in, this PR should work fine when compiled with VS stable, but especially due to all the other C# 9 changes coming in other PRs, as well as the .NET 5 support itself, we should definitely update the CI build of VS to the new release as soon as it drops hopefully next week when .NET 5 is announced at .NET Conf 😄
|
||
// Copy a span to a target row and column with valid lengths | ||
values.CopyTo(array1.GetRow(0)); | ||
values.Slice(0, 4).CopyTo(array1.GetColumn(1)); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should we check the intermediary results? I've noticed these tests are chaining quite a few operations together, so it may be better to help find problems in a particular part if we're validating the first part before moving on? (Or split them as separate tests?)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think I addressed this in 6bb9e5a, let me know if that's alright 😄
|
||
// A couple of tests for invalid parameters, ie. layers out of range | ||
Assert.ThrowsException<ArgumentOutOfRangeException>(() => new Memory2D<int>(array, -1)); | ||
Assert.ThrowsException<ArgumentOutOfRangeException>(() => new Memory2D<int>(array, 20)); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
same here for off-by-one
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Added a few new tests for this and ReadOnlyMemory2D<T>
versions in 7b04c7d 👍
Hello @michael-hawker! Because this pull request has the p.s. you can customize the way I help with merging this pull request, such as holding this pull request until a specific person approves. Simply @mention me (
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Other than cleaning up some no longer needed SuppressMessageAttributes
, It looks good.
[SuppressMessage("StyleCop.CSharp.NamingRules", "SA1312", Justification = "Dummy loop variable")] | ||
[SuppressMessage("StyleCop.CSharp.LayoutRules", "SA1501", Justification = "Empty test loop")] |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There a few SuppressMessageAttributes
left on methods that are no longer needed. It would be good to remove them from methods that no longer need it.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Oh, good catch, forgot about those! 😄
Fixed in 7e67a9c, and also fixed some other leftover warnings in the tests.
Removing the |
## Fixup for #3353 <!-- Add the relevant issue number after the "#" mentioned above (for ex: Fixes #1234) which will automatically close the issue once the PR is merged. --> <!-- Add a brief overview here of the feature/bug & fix. --> ## PR Type What kind of change does this PR introduce? <!-- Please uncomment one or more that apply to this PR. --> - Bugfix <!-- - Feature --> <!-- - Code style update (formatting) --> <!-- - Refactoring (no functional changes, no api changes) --> <!-- - Build or CI related changes --> <!-- - Documentation content changes --> <!-- - Sample app changes --> <!-- - Other... Please describe: --> ## Overview <!-- Please describe the current behavior that you are modifying, or link to a relevant issue. --> `ReadOnlySpan2D<T>.Slice` has the incorrect ordering for the `height` and `width` parameters, which is inverted with respect to those in `Span2D<T>.Slice`. This is an oversight from a refactor in the original PR, and as a result it also causes the `ReadOnlySpan2D<T>.this[Range, Range]` indexer to fail for `ReadOnlySpan2D<T>`. This PR fixes both issues and adds a new series of tests for the two indexers as well. We probably didn't notice this before since those indexers are not available on UWP 🤔 > **NOTE:** this PR technically introduces a breaking change (swapped parameters in the `.Slice` method, but as that's an issue and not intended, and also because they're in the right order in `Span2D<T>`, it's more of a fix than an actual breaking change. ## PR Checklist Please check if your PR fulfills the following requirements: - [X] Tested code with current [supported SDKs](../readme.md#supported) - [ ] ~~Pull Request has been submitted to the documentation repository [instructions](..\contributing.md#docs). Link: <!-- docs PR link -->~~ - [ ] ~~Sample in sample app has been added / updated (for bug fixes / features)~~ - [ ] ~~Icon has been created (if new sample) following the [Thumbnail Style Guide and templates](https://github.com/windows-toolkit/WindowsCommunityToolkit-design-assets)~~ - [X] New major technical changes in the toolkit have or will be added to the [Wiki](https://github.com/windows-toolkit/WindowsCommunityToolkit/wiki) e.g. build changes, source generators, testing infrastructure, sample creation changes, etc... - [X] Tests for the changes have been added (for bug fixes / features) (if applicable) - [X] Header has been added to all new source files (run *build/UpdateHeaders.bat*) - [ ] Contains **NO** breaking changes
The 🦙 (@michael-hawker) asked:
And this is the answer 🚀
PR Type
What kind of change does this PR introduce?
What is the current behavior?
There is no proper way to pass around a 2D memory area in a format that's easy to handle and somewhat similar to how the other
System.Memory
APIs work (ie.Span<T>
,Memory<T>
). This is regardless of whether or not the wrapped memory region is contiguous in memory or not.What is the new behavior?
This PR adds a number of new types and extensions to solve this issue, specifically:
Span2D<T>
ReadOnlySpan2D<T>
Memory2D<T>
ReadOnlyMemory2D<T>
And a series of new extensions for 3D arrays as well.
Note that all these 2D types can be used with arrays of any type, eg. they can both be used to wrap a 2D memory region from a 2D array, a 2D slice for a 3D array, or even just to represent a 1D array as a 2D memory region to make it easier to properly access elements by their spatial coordinates.
API surface (summary)
These two types are also mirrored as
ReadOnlyMemory2D<T>
andReadOnlySpan2D<T>
.PR Checklist
Please check if your PR fulfills the following requirements:
Sample in sample app has been added / updated (for bug fixes / features)Icon has been created (if new sample) following the Thumbnail Style Guide and templatesAdditional notes
The breaking change is the removal of the
Array2DRowEnumerable<T>
andArray2DColumnEnumerable<T>
types, which were used in theGetRow
andGetColumn
APIs. This approach had two problems:GetRow
API was a breaking change when upgrading, as the return type was justSpan<T>
there.The solution, as discussed with @john-h-k and @tannergooding, was to introduce a new, shared
RefEnumerable<T>
type that can be used to traverse both rows and columns, and to use that one for both APIs. Next to them, a new APIGetRowSpan
was also added only on supported runtimes (so not a breaking change), which is equivalent toGetRow
but returning aSpan<T>
value.