Follow

Keep Up to Date with the Most Important News

By pressing the Subscribe button, you confirm that you have read and are agreeing to our Privacy Policy and Terms of Use
Contact

Why Tuple<int,int> takes the same amount of memory as Tuple<ushort,ushort>?

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:

MEDevel.com: Open-source for Healthcare and Education

Collecting and validating open-source software for healthcare, education, enterprise, development, medical imaging, medical records, and digital pathology.

Visit Medevel

  1. When using Dictionary<long, ValueTuple<int,int>>:
    enter image description here
    enter image description here

  2. When using Dictionary<long, ValueTuple<ushort,ushort>>:
    enter image description here
    enter image description here

>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 Pack field controls the alignment of a type’s fields in memory. It affects LayoutKind.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.

Demo @sharplab

Add a comment

Leave a Reply

Keep Up to Date with the Most Important News

By pressing the Subscribe button, you confirm that you have read and are agreeing to our Privacy Policy and Terms of Use

Discover more from Dev solutions

Subscribe now to keep reading and get access to the full archive.

Continue reading