Reputation: 1461
I have a generic dictionary of a struct as key and a class reference as a value.
Dictionary<IntVector2, SpriteRenderer> tileRendererMap;
I retrieve a reference to the renderer via the coordinates and change it like so:
tileRendererMap[tileCoord].color = Color.cyan;
This generates 0.8KB of garbage each time I use it. Is this the default behaviour? I would think that dictionary lookup would be one of the most performant things to do. I need to find a way around this, since I'm working for mobile platform and it's a critical system.
Any ideas what I might be doing wrong or how to get allocation-free lookup?
Edit after more testing:
Using an int as key instead of my custom struct works as expected. No allocations, no problems and I don't think it's a weird thing to use an id as a key. However, for my game I want to store the renderer associated with a specific tile in a grid, which doesn't actually need to be any type of object, because I only care about the grid position. Hence, I thought an IntVector2 might be a practical identifier, but I could work around it, if I had to.
When using my struct I get allocations, which I measure with the Unity3D Profiler. It reports 0.8KB in Dictionary_get_item, specifically DefaultComparer.Equals(). Apparently it's a boxing/unboxing issue, but even when I implement my custom overrides, it still generates garbage, only a little less than before.
My basic struct implementation:
public struct IntVector2
{
public int x, y;
public override bool Equals(object obj)
{
if (obj == null || obj is IntVector2 == false)
return false;
var data = (IntVector2)obj;
return x == data.x && y == data.y;
}
public override int GetHashCode()
{
return x.GetHashCode() ^ y.GetHashCode();
}
}
Edit after accepted answer:
The allocation-free version of my struct when used in a generic dictionar.
public struct IntVector2 : IEquatable<IntVector2>
{
public int x;
public int y;
public IntVector2(int x, int y)
{
this.x = x;
this.y = y;
}
public bool Equals(IntVector2 other)
{
return x == other.x && y == other.y;
}
}
The Dictionary class uses the IEquatable interface instead of the object override Equals, which is why my first implementation was boxing one of the values each time it checked for equality when searching for the key.
Upvotes: 8
Views: 5268
Reputation: 203842
Since you only override Equals
, and don't implement IEquatable<IntVector2>
, the dictionary is forced to box one of the two instances whenever it compares two of them for equality, since it's passing an instance into an Equals
method accepting object
.
If you implement IEquatable<IntVector2>
then the dictionary can (and will) use the version of Equals
that accepts the parameter as an IntVector2
, which won't require boxing.
Upvotes: 14
Reputation: 77876
Your IntVector2
is a struct
type. Not sure what's the point of using a struct
as a dictionary key though? Do remember struct
are value type and so will get value copied for each key and based on the size of that struct may generate that memory footprint.
You may probably want to use a particular property of that struct like. Example your struct
public struct IntVector2
{
string key;
}
Then you can use that property rather like
Dictionary<string, SpriteRenderer> tileRendererMap;
Intvector2 iv2 = new Intvector2();
iv2.key = "test";
tileRendererMap = new Dictionary<string, SpriteRenderer>();
tileRendererMap.Add(iv2.key, new SpriteRenderer{prop1 = value});
Upvotes: 0