Introduction
In
this post I'll shortly describe and demonstrate how the CLR Align & Pack
the object's properties in memory (Managed Heap).
Meaning,
the layout of the properties we'll declare in our object, is not according to
the declaration order.
It
is commonly thought that the order the developer declares the properties would
be the same order they would reside in memory.
However,
the CLR reorders them in the most effective way to fully exploit the object's
memory, so we won't have any unused memory related to that object.
Remark: For
additional detailed information about .Net types - Memory Layout, please review
the .Net Type Internals post.
Word size
'Word'
in computer architecture context means - the basic unit of data
(register) used by the processor, whereas the 'Word Size' refers to the size of
the processor register.
·
On a 32 bit system, the word size is: 32 bits (4
Bytes).
·
On a 64 bit system, the word size is: 64 bits (8
Bytes).
The
virtual memory addresses could be set in the range of the 'word lowest address
value' to the 'word max address value', thus determines the process's 'Virtual
Address Space' in memory.
This
means that:
·
On a 32 bit system: reference types are aligned to the
nearest 4 byte multiple.
Note: Even if the object contains only
1 byte property, its size would be 12 bytes:
-
SBI (4) + MT Pointer (4) + (1 byte aligned to 4 bytes)
= 12 bytes.
·
On a 64 bit system: reference types are aligned to the
nearest 8 byte multiple.
Note: Even if the object contains only
1 byte property, its size would be 24 bytes:
-
SBI (8) + MT Pointer (8) + (1 byte aligned to 8 bytes)
= 24 bytes.
Remark: The StructLayout
& FieldOffset attributes:
As
stated above the order of the properties we'll declare in our object is not the
same order of the object - memory layout.
However,
in case we'll insists on controlling the properties order in memory (for
particular scenarios, e.g. interoperability with COM), we could use the StructLayout
and FieldOffset attributes.
For
an example, please review the 'Controlling properties order & memory layout'
section at the bottom of this post.
Demo-1
In
this demo I'll create few objects and we'll observe theirs sizes in memory, and
the effect of the Alignment & Packing behavior on the total object
size.
-
I'll use Visual Studio different debug windows.
-
And we'll also debug the code in the immediate window,
using SOS DLL extension.
Remark: I'm using 32 bit system for this
example.
1. Create a simple class - Employee:
/// Employee class - Running
under 32bit system.
///
/// Total object size: 16
Bytes
/// - Properties: 2 Bytes + 4 Bytes + 2
Bytes = 8 Bytes
/// - MT Pointer + SBI: + 4 Bytes + 4 Bytes
= 8 Bytes
public class Employee
{
public short Age { get; set; } // Size: 2 Bytes
public int Id { get; set; } // Size: 4 Bytes
public char Letter { get; set; } // Size: 2 Bytes
}
|
I've
created the 'Employee' class with 3 properties and ordered them according to my
wish:
-
Age: 2 bytes
-
Id: 4 bytes
-
Letter: 2 bytes
2. Create an employee instance:
For simplicity, I've
assigned these values.
private static void
AlignmentAndPackingExample()
{
Employee employee = new Employee();
employee.Age =
9;
employee.Id =
4;
employee.Letter = 'A';
}
|
3. Observe the employee instance in memory:
We could
observe the object memory layout in V.S. Memory window.
Remark: For additional detailed
information how to establish this view, please review the .Net Type
Internals post.
Remark: All the values of the addresses
and fields are hexadecimal values.
We could
see that:
-
SBI:
occupied 4 bytes and its value is: 00000000
-
MT Pointer:
occupied 4 bytes and its value is: 001934a4
-
Id:
occupied 4 bytes and its value is: 00000004
-
Letter:
occupied 2 bytes and its value is: 0041 (41Hex=65Dec Ascii for
'A')
-
Age:
occupied 2 bytes and its value is: 0009
If the
CLR was storing the properties according to their declaration order the 2 bytes
properties had to occupy 4 bytes each, since on a 32 bit system reference types
are aligned to the nearest 4 byte multiple.
Meaning
the total object size was:
-
Age: 2 bytes -> aligned to 4 bytes
-
Id: 4 bytes
-
Letter: 2 bytes -> aligned to 4 bytes
Total: 12 + 4 (SBI) + 4 (MT Pointer) = 20 bytes
Whereas,
using the alignment & packing behavior, the total object size is:
-
Id: 4 bytes
-
Letter: 2 bytes + Age: 2 bytes -> aligned & packed
to 4 bytes
Total: 8 + 4 (SBI) + 4 (MT Pointer) = 16 bytes
Conclusion
Even though I declared the order of the properties as:
-
Age: 2 bytes
-
Id: 4 bytes
-
Letter: 2 bytes
The CLR aligned & packed the Letter and Age fields
and order them as:
-
Id: 4 bytes
-
Letter: 2 bytes & Age: 2 bytes (total 4 bytes)
If the CLR wasn't maintaining this behavior, we would
get a lot of wasted and unreachable memory due to the fact that some of the
properties are less than 4 bytes in a 32 bit system, and less than 8 bytes in a 64 bit system.
Demo-2
In
this demo I'd like to use SOS.dll extension to observe some objects sizes in
memory.
1. Examine Employee size
/// Employee class - Running
under 32bit system.
///
/// Total object size: 16
Bytes
/// - Properties: 2 Bytes + 4 Bytes + 2
Bytes = 8 Bytes
/// - MT Pointer + SBI: + 4 Bytes + 4 Bytes
= 8 Bytes
public class Employee
{
public short Age { get; set; } // Size: 2 Bytes
public int Id { get; set; } // Size: 4 Bytes
public char Letter { get; set; } // Size: 2 Bytes
}
|
I've
performed the following:
-
Load sos.dll.
-
Reviewed the type on the Managed Heap.
-
Reviewed the object data.
.load sos.dll
extension C:\Windows\Microsoft.NET\Framework\v2.0.50727\sos.dll
loaded
---
!DumpHeap -type TestPerfExample.Employee
PDB symbol for mscorwks.dll not loaded
Address MT
Size
01d29424 002934a4
16
total 1 objects
Statistics:
MT Count
TotalSize Class Name
002934a4
1 16
TestPerfExample.Employee
Total 1 objects
---
!do 01d29424
Name: TestPerfExample.Employee
MethodTable: 002934a4
EEClass: 00291964
Size: 16(0x10) bytes
…\TestPerfExample\bin\Debug\TestPerfExample.exe)
Fields:
MT Field
Offset Type
VT Attr Value Name
6740ed4c
4000001 8 System.Int16 1 instance 9
67412f74
4000002 4 System.Int32 1 instance 4
67411a08
4000003 a System.Char 1 instance 41
|
2. Class with 1 property
I've created the
following class with only 1 property of 2 bytes size.
public class Rectangle
{
public short Id { get; set; } // Short is Int16 - 2 bytes
}
private static void
CreateRectangleExample()
{
Rectangle rectangle = new Rectangle();
}
|
I
performed the following:
-
Load sos.dll.
-
Reviewed the type on the Managed Heap.
.load sos.dll
extension
C:\Windows\Microsoft.NET\Framework\v2.0.50727\sos.dll loaded
---
!DumpHeap -type TestPerfExample.Rectangle
Address MT
Size
01b19424 001a347c
12
total 1 objects
Statistics:
MT Count
TotalSize Class Name
001a347c
1 12
TestPerfExample.Rectangle
Total 1 objects
---
!ObjSize 01b19424
sizeof(01b19424) = 12 (0xc) bytes
(TestPerfExample.Rectangle)
|
We could
see that the total object size is 12 bytes, even though it had only 1 property
of size 2 bytes.
The SBI
and MT Pointer are 8 bytes + the 2 bytes of the property aligned to 4 bytes,
thus the total is 12 bytes.
3. Class without properties
I've created the
following class without any properties, to examine its size.
public class Point
{
// Empty class
}
private static void
CreatePointExample()
{
Point point = new Point();
}
|
I
performed the following:
-
Load sos.dll.
-
Reviewed the type on the Managed Heap.
.load sos.dll
extension C:\Windows\Microsoft.NET\Framework\v2.0.50727\sos.dll
loaded
---
!DumpHeap -type TestPerfExample.Point
PDB symbol for mscorwks.dll not loaded
Address MT
Size
01c29424 003b3438
12
total 1 objects
Statistics:
MT Count
TotalSize Class Name
003b3438
1 12
TestPerfExample.Point
Total 1 objects
|
We could
see that the Point size is 12 bytes, even though we haven't declared a single
property.
There are
off course the SBI and the MT Pointer fields, each occupies 4 bytes of memory,
but the additional 4 bytes relates to the Alignment & Packing
behavior!
Conclusion
We managed to demonstrate how the Alignment &
Packing behavior effects the objects sizes on the Managed Heap to exploit
the maximum memory space for that object, and to prevent unused and unreachable
memory.
Controlling properties order & memory
layout
Prior
to completing this post, I'd like to show a quick example of how to control the
order and memory layout of our object's properties.
As
described above, some scenarios dictate this control, and for that we'll use
the StructLayout
& FieldOffset attributes.
/// Employee class - Running
under 32bit system.
///
/// Total object size: 16
Bytes
/// - Properties: 2 Bytes + 4 Bytes + 2
Bytes = 8 Bytes
/// - MT Pointer + SBI: + 4 Bytes + 4 Bytes
= 8 Bytes
[StructLayout(LayoutKind.Explicit)]
public class Employee
{
[FieldOffset(offset: 0)]
public short Age; // Size: 2 Bytes
[FieldOffset(offset: 4)]
public int Id; // Size: 4 Bytes
[FieldOffset(offset: 8)]
public char Letter; // Size: 2 Bytes
}
|
For
this example, I used the StructLayout attribute with the LayoutKind.Explicit.
This
means that the members of the object are laid out in memory the way I declared
them using the FieldOffset attribute.
Now,
when we'll examine the 'Employee' memory layout and size, we'll get the
following:
Memory
layout:
We
could see from the memory layout above that the Age and Letter fields occupy 4
bytes each, even though they only need 2 bytes each. Thus, in this way, we are
creating 4 bytes of unused and unreachable memory related to this objects.
Size:
20 Bytes
.load sos.dll
extension
C:\Windows\Microsoft.NET\Framework\v2.0.50727\sos.dll loaded
---
!DumpHeap -type TestPerfExample.Employee
PDB symbol for mscorwks.dll not loaded
Address MT
Size
01c79424 00483474
20
total 1 objects
Statistics:
MT Count
TotalSize Class Name
00483474
1 20 TestPerfExample.Employee
Total 1 objects
---
!ObjSize 01c79424
sizeof(01c79424) = 20 (0x14) bytes (TestPerfExample.Employee)
|
Value Types – Structures
One
last remark concerning Value Types (structures) Memory Layout.
When
creating a structure, the CLR stores its properties in the Thread Stack
in the order in which they were declared.
This
would mean that uncareful declaration of some properties in a structure could
cause a lot of unused and unreachable memory.
Example:
Consider
the following struct:
public struct StructPerson
{
public short Value1 { get; set; }
public int Value2 { get; set; }
public short Value3 { get; set; }
}
|
It
appears as if this struct size should be: 8 Bytes.
However
in fact we could observe the following code and find out that it size is
actually: 12 Bytes.
I've
created 2 StructPerson and
assigned values.
private static void
CreateStructPersonExample()
{
StructPerson
structPerson1 = new StructPerson();
structPerson1.Value1
= 1;
structPerson1.Value2
= 2;
structPerson1.Value3
= 3;
StructPerson
structPerson2 = new StructPerson();
structPerson2.Value1
= 4;
structPerson2.Value2
= 5;
structPerson2.Value3
= 6;
unsafe
{
Console.WriteLine(sizeof(StructPerson));
}
}
|
Memory layout on the Thread Stack:
Now, if we'll examine the Thread Stack memory, we'll notice the following:
-
We've declared 2 StructPerson
variables on the Thread Stuck.
-
We could see the Stuck Memory using the EBP (Base
Pointer) register.
-
The Stuck Memory starts in address: 0x05D6E5C4 and
the addresses values are descending. Meaning the stack frame starts in a high
address and descent as the stack fills in.
-
We could see that in a Value Type the SBI & MT Pointer
fields don’t exist.
-
The struct size: we could see that each field in the struct occupies 4
bytes of memory, even the short fields
(value1, value3), thus its size is 12 bytes.
We could
fix this problem quite easily actually, using the StructLayout &
FieldOffset attributes.
In this
example I only used the StructLayout attribute with the LayoutKind.Auto.
This means
that the CLR would automatically choose an appropriate layout for the members
of this struct.
[StructLayout(LayoutKind.Auto)]
public struct StructPerson
{
public short Value1 { get; set; }
public int Value2 { get; set; }
public short Value3 { get; set; }
}
|
Memory layout on the Thread Stack:
Now we could see that Value1 and Value3 which are 2 bytes each are Aligned & Packed together.
And the
total size of the StructPerson is 8 bytes (instead as 12
bytes since the members were ordered 'wrong').
The End
Hope you enjoyed!
Hope you enjoyed!
Appreciate your comments…
Yonatan Fedaeli
No comments:
Post a Comment