When ValueTuple<int,int> or ValueTuple<ushort,ushort> are in Dictionary, they use the same amount of memory. Why type Dictionary<long, ValueTuple<int,int>> takes exactly the same amount of memory as Dictionary<long, ValueTuple<ushort,ushort>>??
I built a very simple console app to test this. The app was build under .NET7 and it’s running on Windows10Pro x64:
var test = new Dictionary<long, (ushort foo, ushort bar)>();
for (long i = 0; i < 3000000; i++)
{
test.Add(i, (100, 100));
}
Console.WriteLine($"Click to close");
Console.Read(); // <--- Get Snapshot while waiting for key
Console.WriteLine($"Total keys: {test.Count}"); // <--- This is needed so that GC doesn't flush "test"
It turns out, that it doesn’t matter if foo and bar variables are int or ushort. It uses exactly the same memory in both cases. I tested it with JetBrains dotMemory. Here are some screenshots:
>Solution :
The reason is that dictionary stores not just value tuples, but KeyValuePair<TKey, TValue> structs (and padding). The tuples itself will have different sizes:
unsafe
{
Console.WriteLine(sizeof((int,int))); // 8
Console.WriteLine(sizeof((ushort,ushort))); // 4
}
But due to them being wrapped in KeyValuePair‘s and padding they will take all 16 bytes (for x64 process):
unsafe
{
Console.WriteLine(sizeof(KeyValuePair<long, (ushort,ushort)>)); // 16
Console.WriteLine(sizeof(KeyValuePair<long, (int,int)>)); //16
// and just for fun
Console.WriteLine(sizeof(KeyValuePair<int, (ushort,ushort)>)); // 8
}
For more info you can check StructLayoutAttribute.Pack remarks (LayoutKind.Sequential is default for structs):
The
Packfield controls the alignment of a type’s fields in memory. It affectsLayoutKind.Sequential. By default, the value is 0, indicating the default packing size for the current platform. The value of Pack must be 0, 1, 2, 4, 8, 16, 32, 64, or 128:
The fields of a type instance are aligned by using the following rules:
- The alignment of the type is the size of its largest element (1, 2, 4, 8, etc., bytes) or the specified packing size, whichever is smaller.
- Each field must align with fields of its own size (1, 2, 4, 8, etc., bytes) or the alignment of the type, whichever is smaller. Because the default alignment of the type is the size of its largest element, which is greater than or equal to all other field lengths, this usually means that fields are aligned by their size. For example, even if the largest field in a type is a 64-bit (8-byte) integer or the Pack field is set to 8, Byte fields align on 1-byte boundaries, Int16 fields align on 2-byte boundaries, and Int32 fields align on 4-byte boundaries.
- Padding is added between fields to satisfy the alignment requirements.



