And, as you may imagine, this will cause a drastic performance impact if these instances are stored in a hash set or a hash table. For instance, if you’re unlucky enough and the first field of your struct has the same value for most instances, then a hash function will provide the same result all the time. This is a reasonable behavior unless it’s not. hash1 and hash2 are the same and hash1 is different from hash3 Var hash3 = new Location(path: "1", position: 42).GetHashCode() Var hash2 = new Location(path: "", position: 1).GetHashCode() Var hash1 = new Location(path: "", position: 42).GetHashCode() Public Location(string path, int position) => (Path, Position) = (path, position) (***) Based on the comment in the CoreCLR repo, this behavior may change in the future.
STRUKT DICTIONARY CODE
So, the CLR authors decided to trade speed over the distribution and the default GetHashCode version just returns a hash code of a first non-null field and “munges” it with a type id (***) (for more details see RegularGetValueTypeHashCode in coreclr repo at github). But the only way to get a hash code of a field in a ValueType method is to use reflection. The canonical hash function of a struct “combines” hash codes of all the fields. In some cases, it’s possible to achieve them both, but it is hard to do this generically in ValueType.GetHashCode. An implementer of a hash function faces a dilemma: make a good distribution of the hash function or to make it fast. Potential collisions of the default GetHashCode implementation.For instance, in Core CLR 2.1 the JIT compiler knows about Enum.HasFlag and emits a very optimal code that causes no boxing allocations. (**) Unless the method is a JIT intrinsic. The way the CLR is designed, every call to a member defined in System.ValueType or System.Enum types cause a boxing allocation (**).
But there are a couple of reasons why they won’t be as efficient as a custom version written by hand (or generated by a compiler) for a specific type. The CLR authors tried their best to make the default implementations of Equals and GetHashCode for value types as efficient as possible. Why the default implementations are so slow? The versions that could easily affect an application’s end-to-end performance in a very significant way. If a struct does not provide Equalsand GetHashCode, then the default versions of these methods from System.ValueType are used. NET Core 2.1 and as I’ve mentioned in the previous post, now you can mitigate the issue with a custom HasFlag implementation for the older runtimes.īut the issue we’re talking about today is different. The issues are real but they’re unlikely to be visible in regular applications.
The same is true for defensive copies caused by non-readonly structs in the readonly contexts. Enum.HasFlag is not very efficient (*), but unless it is used on a very hot path, it would not cause a severe issue for your product. Not every potential performance issue affects an end-to-end time of your application. Then we’ll look at a performance bug that occurred in my project and at the end we’ll discuss some tools that can help to avoid the issue altogether. To better understand the importance and the rationale behind this advice we’re going to look at the default behavior to see why and where the performance hit comes from. If you’re familiar with C#, then you most likely heard that you should always override Equals and GetHashCode for custom structs for performance reasons.