C
C#14mo ago
DoctorGurke

❔ Excessive runtime allocation using Dictionary with struct key TryGetValue

Doing some optimization testing using spatial partitioning of 2D scenes and have hit a weird runtime allocation problem that I've tracked down to Dictionary.TryGetValue. I am storing Chunk instances in a Dictionary with a custom int2 struct type that implements IEquatable. Here's my index type:
public struct ChunkCoord : IEquatable<ChunkCoord>
{
public int X { get; set; }
public int Y { get; set; }

public ChunkCoord(int x, int y)
{
X = x;
Y = y;
}

public bool Equals(ChunkCoord coord)
{
return X == coord.X && Y == coord.Y;
}
}
public struct ChunkCoord : IEquatable<ChunkCoord>
{
public int X { get; set; }
public int Y { get; set; }

public ChunkCoord(int x, int y)
{
X = x;
Y = y;
}

public bool Equals(ChunkCoord coord)
{
return X == coord.X && Y == coord.Y;
}
}
Any tips to fix or avoid this?
No description
6 Replies
bigbitc++
bigbitc++14mo ago
Off the top of my head, maybe try overriding the GetHashCode?
Omnissiah
Omnissiah14mo ago
we probably need some info to understand this problem like how big is the dictionary, where are you using ChunkCoord (are you saying it's the key?), what are your expectations, also why struct and not class
JakenVeina
JakenVeina14mo ago
yeah, the perf capture shows that ChunkCoord is TKey, and TValue is... System.__Canon The hell is System.__Canon? yes, you very much should be overriding Equals() for anything that implements IEquatable<>, and you should be overriding GetHashCode() for anything that overrides Equals(). Alternatively, just make the whole struct a record struct. However, I feel like we're still missing something from that perf capture. Do you have an option set to have it stop tracking calls as soon as it hits an external call? Cause if the problem is your stuct implementation, you should be seeing Dictionary<>.TryGetValue() hop BACK into your own code for at least a .Equals() call. Otherwise, I'd have to say that this profile shows the allocations have nothing to do with the struct, and are somehow occurring within .TryGetValue() itself. Like it's allocating arrays for intermediate results or something.
reflectronic
reflectronic14mo ago
__Canon is a stand-in for a reference type the problem here is almost certainly that GetHashCode is not overridden, yes the default implementation of it for a struct is not good. it is slow and results in frequent collisions. it also requires that the struct is boxed, which is almost certainly the source of the allocations that is why the allocations show up in TryGetValue and not elsewhere. calling .GetHashCode() itself requires an allocation i agree that
public record struct ChunkCoord(int X, int Y);
public record struct ChunkCoord(int X, int Y);
nicely solves this issue
JakenVeina
JakenVeina14mo ago
that'll do it, then for reference, the proper way to write your own GetHashCode() method, if you ever need to is...
public override int GetHashCode()
=> HashCode.Combine(X, Y);
public override int GetHashCode()
=> HashCode.Combine(X, Y);
at least, starting with .NET Core 2.1
Accord
Accord14mo ago
Was this issue resolved? If so, run /close - otherwise I will mark this as stale and this post will be archived until there is new activity.
Want results from more Discord servers?
Add your server