Removed unnecessary MemoryMarshal.GetReference#32669
Conversation
| ReadOnlySpan<char> span = s; | ||
| Assert.Equal(s.Length, span.Length); | ||
| fixed (char* stringPtr = s) | ||
| // removing MemoryMarshal breaks this test |
There was a problem hiding this comment.
here we try to compare pointer to empty string with pointer of empty span
|
|
||
| internal void WriteCore(ReadOnlySpan<byte> buffer) | ||
| { | ||
| if (buffer.IsEmpty) |
| int size = -1; | ||
|
|
||
| // T-REC-X.690-201508 sec 9.2 | ||
| // MemoryMarshal.GetReference is used here to handle empty string case correctly |
There was a problem hiding this comment.
here empty string is a valid input (there are tests for it) and removing GetReference breaks the logic since we get null for empty span
|
Are all of these changes necessary to fix bugs? Or if not, is it possible any will regress performance on hot paths, due to the extra checks involved? For ones that are fixing bugs, we should make sure we have tests that would have failed without these changes. |
|
Changes under CoreLib should be made in the coreclr repo. I know it's a little confusing, but that code isn't necessarily used/compiled/tested in corefx; making the changes in coreclr will help validate it in CI. |
|
@stephentoub this is just a cleanup, new code is equivalent except for empty span case and majority of places already have precondition checks for it (hence tests are passing) |
Thanks (and for working on this). My concern still stands as to a) some of these changes being made in corefx instead of coreclr, and b) whether some of these should remain as they were in order to avoid the extra checks. In many/most cases, the extra checks won't matter and the simpler code you're changing it to is better, but I question whether a few cases might regress perf? |
|
@stephentoub I will port needed parts to coreclr if we decide to proceed with such changes. Regarding b) - that`s why i added comments in problematic places, other places are just result of "Find and Replace" :) |
Those are the places I'm talking about. Currently it's using: and your change makes it use: which involves an extra branch to ensure that null is returned when the length is 0. In the vast majority of uses, that extra branch won't matter. But there are some places in these code bases where methods are used on such hots paths and in such tight loops that extra branches like that, and increases in code sizes, do matter. It's quite possible that all of your changes fall into the "this doesn't matter" case, in which case great. But that's my question. |
|
@stephentoub Okay, i get it now, thank you for detailed explanation. That totally makes sense, but how do i determine what to revert? |
@JeremyKuhne, you opened #32099... what did you have in mind here? @eerhardt just faced a case in his work where he saw upwards of a 20% dip in perf due to relying on the default use of GetPinnableReference rather than MemoryMarshal.GetReference, so a sweeping PR that switches from the latter to the former makes me nervous, even though I think it's unlikely to affect the majority of these uses. @lkts, regardless, the changes under src/Common/src/CoreLib should be reverted in this PR. Even if it's decided those changes should be made, they should be done via a PR to the coreclr repo rather than modifying the mirrored code in corefx. Thanks. |
Consistency. Opened it based on @jkotas feedback: #32072 (comment) |
See dotnet/machinelearning@ec4c943 where I am adding MemoryMarshal.GetReference to all ML.NET's calls to pin a span inside the CpuMath library. On my machine the K-Means benchmark went from: |
e89a12b to
e34a03e
Compare
|
@stephentoub @eerhardt I see, thank you. I guess this PR and issue should be closed then and such changes should be done in scope of other changes to these files if it seems applicable. |
|
The array pinning has the extra checks for empty/null by default just like pinning for Span without 'MemoryMarshal.GetReference'. There is no fundamental reason why regular pinning of Span should be slower than regular pinning of Span. I agree that it would be good idea to understand the root cause of the performance difference in CpuMath library - since you do not see the performance regression with array pinning that has these empty/null checks as well. E.g. You may be just seeing side-effects of cache or loop alignment that have nothing to do with pinning. Or you may be seeing JIT defficiency that we want to fix. And since the array pinning has the extra checks for empty/null by default, folks are sometimes micro-optimizing it via patterns that avoid it. For example, if you know that the array is not empty, you can do this: fixed (byte* p = &array[0])
{
...
}This pattern tends to produce slightly smaller and faster code than regular array pinning. It is similar to |
That's what Eric's array code was doing. The GetPinnableReference added checks not present in the array code. |
|
Even with everything mentioned here so far, the performance results in #32669 (comment) do not make quite sense to me. @eerhardt Could you please confirm:
|
All the existing CpuMath code pins the array with |
That can explain it. @eerhardt Could you please run the same on .NET Core 3.0 runtime ? |
@jkotas, which issues were those? Should I infer from your comments that you believe it's safe to search/remove GetReference usage in exchange for the default GetPinnableReference, without further perf validation (in particular for the coreclr cases that were removed from this PR)? |
E.g. https://github.com/dotnet/coreclr/issues/18270
I agree with you that we should not do a blank search/remove without any perf-validation. This change should be split into multiple PRs:
My most recent comments were trying to suggest that the ML.NET results posted above do not represent the current state of .NET Core 3.0 well. The difference between the array pinning case (even the micro-optimized one with |
I've run each commit 5 times using the latest 3.0 from Arrays: (the "base" commit) Pin Span (default): (Changing to use Spans commit) Pin MemoryMarshal.GetReference(Span):(Changing to call MemoryMarshal commit) You can see there is some variation run-to-run, but pinning the span in the "default" way is consistently higher. |
|
Last week I also wrote up a micro benchmark, which I believe repros the core issue. Here is the change with the benchmark, and a side-by-side "Span" overload to Running the above benchmark produces: And here is the change to call Running the benchmark again: |
|
Thanks. I have tweaked GetPinnableReference implementation in dotnet/coreclr#20428 to make it much closer to pinning using |
|
Thanks everyone for productive discussion! Glad that improvement possibility was discovered here. As a result I suggest updating https://github.com/dotnet/corefx/issues/32099 with steps provided by @jkotas and other relevant information (or creating new issue). Code changes here do not make sense anymore so closing it. |
Fixes https://github.com/dotnet/corefx/issues/32099
There are few places where it breaks the logic, I marked them with comments and will highlight here in PR.
@JeremyKuhne @danmosemsft @jkotas