Drastically reduce memory consumption of Comparer<T> for enums#5503
Drastically reduce memory consumption of Comparer<T> for enums#5503jkotas merged 1 commit intodotnet:masterfrom
Conversation
|
Couldn't Shouldn't there be tests? In particular funky conditions such as |
They don't return the same value (see here). I did it since all of the other specialized (generic) comparers in the file did it, as well.
Yeah, IK. I'll probably add corresponding tests to the corefx repo if this gets merged. |
|
This appears to change the sorting order for unsigned enums: |
|
Regarding the hash code I'd propose changing this everywhere. The Name pattern is slower and more prone to collisions. |
Fixed. Thank you for pointing that out!
I've changed the algorithm for the new classes I've introduced; I'll change the other |
|
I'd pull The type check is a JIT intrinsic. It's faster than doing it in any other way. Also, I'd make all comparer classes sealed to document that they are leaf classes. Maybe that even has some performance benefit here and there. Thank you for contributing, btw :) |
There was a problem hiding this comment.
Can you double check that you are actually going to get the right TypeCode after this change?
There was a problem hiding this comment.
@AlexGhiondea As far as I can see this will always have the same effects as if Enum.GetUnderlyingType was excluded: https://github.com/dotnet/coreclr/blob/master/src/mscorlib/src/System/RtType.cs#L3400
However, it looks like explicitly calling GetUnderlyingType can avoid another P/Invoke + virtual method call (which would degrade performance), so I'll change this back for now.
|
@dotnet-bot test this please |
|
Is this going to cause back compat issues with serialization? I wonder if we should do something similar to what we did here to make sure we can still serialize/deserialize data that does not know about these new types. |
|
@AlexGhiondea Thanks for pointing that out, I've made sure that it gets serialized as @jkotas PTAL |
There was a problem hiding this comment.
I do not think that the extra level of inheritance is a win here. The generic instantiations over value types are not small (couple hundred bytes each). This extra level of inheritance will make us create two instead of just one.
This should inherit directly like the EqualityComparer.
There was a problem hiding this comment.
@jkotas OK, got it. I'll move out all of the shared logic from (and delete) the EnumComparer class as well.
|
@dotnet-bot Test FreeBSD x64 Checked Build |
This does not sound right. This change should make things significantly faster. Could you please drill into what is going on to explain these results? |
|
In the benchmark program the array is all zeroes. This might be unrepresentative of real loads in some way. At the very least the Sort call is just a linear traversal of the array with no mutations. Not sure why this would matter, though. For some reason I cannot profile that console app with VS15 on 4.6 Desktop. The call stack within Array.Sort is just gone. I tried two profilers and I also stopped the debugger. The debugger can't even stop there, it always stops at Stopwatch.GetTimestamp after the current sort run is done. Why could that be the case?! |
|
Maybe we are entering |
|
This fixes the issue and uses the .NET-based sorting and comparing code: |
|
LGTM. Thanks! |
|
Hooray! I looked at all test files of the pattern |
|
The comparer tests also seem quite ad-hoc. I'd do it like that: And then: That makes it easy to add cases, cleans up the very verbose tests and reduces the number of cases by half. Can be extended for other cases as well such as And to be clear, the code shown above is a rough sketch. If this approach is being liked it must be done properly, the |
|
@GSPP I definitely agree about the need to update the test coverage - I have opened https://github.com/dotnet/corefx/issues/9379 on it. |
This addresses #5314 by specializing
Comparer<T>if the typeof T can be determined to be an enum. It follows similar logic to what's done inEqualityComparertoday, with the exception that it doesn't attempt to specialize for sbytes/shorts, since we don't have to deal with hash codes.Additional changes/notes:
Enum.GetUnderlyingTypeshouldn't be necessary before the call toType.GetTypeCode, as if you doType.GetTypeCode(typeof(StringSplitOptions))it will print out Int32.TypeCodewas <= 32 bits without introducing unnecessary branches. I made sure to leave a comment explaining what it was intended to check, and didn't change the version inEqualityComparer.Serializableattributes to the new classes, in case anyone expectsComparer<T>.Defaultto always be serializable.Perf impact
I made this console app to test the new changes.
Interestingly, the timings don't seem to be too much affected, regardless of how much I alter the iterations/length of the array. However with the old implementation the app consumes ~20K memory in Task Manager, while it consumes ~2K with the new one, so I guess while it may not have much impact in terms of speed it helps a lot with memory usage.
cc: @jkotas @omariom @AlexGhiondea