List optimizes erratically - c#

If I execute the below code and do not press any key on the console
class Program
{
static void Main(string[] args)
{
List<Test> list = new List<Test>(1) {new Test()};
Console.ReadKey();
GC.KeepAlive(list);
var x = list[0];
Console.WriteLine((x.ToString()));
}
}
class Test
{
public override string ToString()
{
return "Empty object";
}
}
Then upon analyzing the array in windbg I see the list do not contain the test object I added.
The element in the 0'th position is something else which i am not sure of
However if i add a string property to my Test class like so
class Test
{
public string Name = "Rohit";
public override string ToString()
{
return "Empty object";
}
}
Then this time windbg reveals the object
Can someone please help explain what is going on? I test the above using Visual Studio 2015 (.net 4 compatability mode) on x64 windows 7
Aside: Even though i requested for the list size to be one (for my test), i see it is going with the default size of 128. So basically the initial capacity is based on some heuristics etc?

From List to the object
From the comments I see some confusion whether a List<T> stores its items in an Object[] or T[].
I compiled the program in VS 2013.4 for .NET 4.0 and I'm debugging in WinDbg 6.2.9600:
0:007> !dumpheap -stat
Statistics:
MT Count TotalSize Class Name
000007feed598130 1 24 System.Security.HostSecurityManager
000007feed597158 1 24 System.Collections.Generic.ObjectEqualityComparer`1[[System.Type, mscorlib]]
000007fe91bd40c0 1 24 ConsoleWriteLine.Test
000007feed592090 1 28 System.Char[]
000007feed5980b8 1 32 System.Security.Policy.Evidence+EvidenceLockHolder
000007feed5975e8 1 32 System.Security.Policy.AssemblyEvidenceFactory
000007feed5974a0 1 32 Microsoft.Win32.SafeHandles.SafePEFileHandle
000007feed594810 1 32 System.Text.DecoderReplacementFallback
000007feed594780 1 32 System.Text.EncoderReplacementFallback
000007feed536fd8 1 40 Microsoft.Win32.Win32Native+InputRecord
000007fe91bd4150 1 40 System.Collections.Generic.List`1[[ConsoleWriteLine.Test, ConsoleWriteLine]]
000007feed591480 1 48 System.SharedStatics
000007feed5945a8 1 56 System.Text.UnicodeEncoding
000007feed5943e0 1 56 System.Reflection.RuntimeAssembly
000007feed598038 1 64 System.Threading.ReaderWriterLock
000007feed597548 1 64 System.Security.Policy.PEFileEvidenceFactory
000007feed592610 1 64 System.Security.PermissionSet
000007feed593af0 1 72 System.RuntimeFieldInfoStub
000007feed592478 1 72 System.Security.Policy.Evidence
000007feed5913e8 3 72 System.Object
000007feeced7d10 1 80 System.Collections.Generic.Dictionary`2[[System.Type, mscorlib],[System.Security.Policy.EvidenceTypeDescriptor, mscorlib]]
000007feed591e00 1 128 System.AppDomainSetup
000007feed591310 1 160 System.ExecutionEngineException
000007feed591298 1 160 System.StackOverflowException
000007feed591220 1 160 System.OutOfMemoryException
000007feed591038 1 160 System.Exception
000007feed591540 1 216 System.AppDomain
00000000002e9e60 8 216 Free
000007feed591388 2 320 System.Threading.ThreadAbortException
000007feed593920 4 492 System.Int32[]
000007feed597fd8 3 720 System.Collections.Generic.Dictionary`2+Entry[[System.Type, mscorlib],[System.Security.Policy.EvidenceTypeDescriptor, mscorlib]][]
000007feed592eb8 21 1176 System.RuntimeType
000007feed590e08 37 2786 System.String
000007feed524918 8 34808 System.Object[]
Total 112 objects
Compared to your output, I have no String[], no Type[]and no Test[]. Instead I have 8 Object[]. Similar to you, I have only 1 List<T>. Let's find out which array it uses. The steps should be the same on your machine, but you might get Test[] as the result.
Step 1: dump all List<T>:
0:007> !dumpheap -mt 000007fe91bd4150
Address MT Size
00000000021a2de8 000007fe91bd4150 40
Statistics:
MT Count TotalSize Class Name
000007fe91bd4150 1 40 System.Collections.Generic.List`1[[ConsoleWriteLine.Test, ConsoleWriteLine]]
Total 1 objects
Step 2: dump the only List<T> which is there:
0:007> !do 00000000021a2de8
Name: System.Collections.Generic.List`1[[ConsoleWriteLine.Test, ConsoleWriteLine]]
MethodTable: 000007fe91bd4150
EEClass: 000007feecf7ea08
Size: 40(0x28) bytes
File: C:\Windows\Microsoft.Net\assembly\GAC_64\mscorlib\v4.0_4.0.0.0__b77a5c561934e089\mscorlib.dll
Fields:
MT Field Offset Type VT Attr Value Name
000007feed524918 4000cd1 8 System.Object[] 0 instance 00000000021a2e10 _items
000007feed593980 4000cd2 18 System.Int32 1 instance 1 _size
000007feed593980 4000cd3 1c System.Int32 1 instance 1 _version
000007feed5913e8 4000cd4 10 System.Object 0 instance 0000000000000000 _syncRoot
000007feed524918 4000cd5 8 System.Object[] 0 static <no information>
See the property _items of type Object[] here.
Step 3: dump the backing Object[]:
0:007> !da 00000000021a2e10
Name: ConsoleWriteLine.Test[]
MethodTable: 000007feed524918
EEClass: 000007feecf77f58
Size: 40(0x28) bytes
Array: Rank 1, Number of elements 1, Type CLASS
Element Methodtable: 000007fe91bd40c0
[0] 00000000021a2e38
There is an interesting finding: when dumping the array, it figured out it is actually a Test[]. It might be that your version of SOS is just smarter than mine and displays types correctly in !dumpheap -stat. My version is 4.0.30319.34209.
Step 4: dump the only object of the array
0:007> !do 00000000021a2e38
Name: ConsoleWriteLine.Test
MethodTable: 000007fe91bd40c0
EEClass: 000007fe91ce23d8
Size: 24(0x18) bytes
File: E:\...\bin\Debug\ConsoleWriteLine.exe
Fields:
None
This is a the test object with no fields, just as expected.
What else could be helpful?
You have picked the second Object[] that was listed by !dumpheap -mt 7fee816f150. I don't know why you have chosen that one, but probably it's because you detected the difference in that array. You have then dumped the object in that array, which was an empty string. The second time, this list contained one more string. I could reproduce this.
To find out more about this, use !gcroot to see where an object is used. Since .NET objects are like pointers, two arrays can point to the same object (string), therefore use -all as parameter.
0:007> !gcroot -all 00000000021a1420
Thread 109c:
000000000013e8c0 000007feedbca151 System.Console.ReadKey(Boolean)
rdi: (interior)
-> 00000000121a1038 System.Object[]
-> 00000000021a1420 System.String
HandleTable:
00000000008517e8 (pinned handle)
-> 00000000121a32e8 System.Object[]
-> 00000000021a1420 System.String
00000000008517f8 (pinned handle)
-> 00000000121a1038 System.Object[]
-> 00000000021a1420 System.String
Found 3 roots.
Another helpful command might be !dso to display objects referenced by the stack (e.g. local variables):
0:007> ~0s
0:000> !dso
OS Thread Id: 0x109c (0)
RSP/REG Object Name
000000000013E950 00000000021a2f10 System.Object
000000000013E9F0 00000000021a2e38 ConsoleWriteLine.Test
000000000013EA00 00000000021a2de8 System.Collections.Generic.List`1[[ConsoleWriteLine.Test, ConsoleWriteLine]]
000000000013EA10 00000000021a2de8 System.Collections.Generic.List`1[[ConsoleWriteLine.Test, ConsoleWriteLine]]
000000000013EA28 00000000021a2de8 System.Collections.Generic.List`1[[ConsoleWriteLine.Test, ConsoleWriteLine]]
000000000013EA30 00000000021a2de8 System.Collections.Generic.List`1[[ConsoleWriteLine.Test, ConsoleWriteLine]]
000000000013EA38 00000000021a2de8 System.Collections.Generic.List`1[[ConsoleWriteLine.Test, ConsoleWriteLine]]
000000000013EA40 00000000021a2e38 ConsoleWriteLine.Test
000000000013EA48 00000000021a2e38 ConsoleWriteLine.Test
000000000013EA80 00000000021a2dc8 System.Object[] (System.String[])
000000000013EBB8 00000000021a2dc8 System.Object[] (System.String[])
000000000013ECD8 00000000021a2dc8 System.Object[] (System.String[])
000000000013EEA8 00000000021a2dc8 System.Object[] (System.String[])
000000000013F478 00000000021a1440 System.SharedStatics

The alternative way to look at this would be from the other end: decompile the code of List<T> (or look at it on GitHub if you wanted the .NET 5 implementation).
Using JetBrains dotPeek against 64-bit mscorlib I see this (with omissions for readability):
public class List<T> : IList<T>, ICollection<T>, IList, ICollection, IReadOnlyList<T>, IReadOnlyCollection<T>, IEnumerable<T>, IEnumerable
{
private T[] _items;
From intuition, I'd suggest that the difference between your two code samples is because that the compiler is smart: your first Test class has no state, and so it's essentially equivalent to a static class - there can never be any difference between multiple instances of the class. I'd imagine the compiler will optimise that away entirely for this reason. Once you add the field, you have state to store in the object and so the optimisation can't be applied.
As Henk Holterman pointed out in comments, the fact that your string literal might appear in an object array is incidental: a string literal in code will be interned by the compiler and will appear in data structures other than the one you've declared as an optimisation. That an object[] exists in memory with your string in it doesn't mean that it's being used as backing store by List<T>.
Regarding the default size of your list: you specify size in items, not bytes. Your single instance of array ConsoleApplication1.Test[] has a size of 32 bits, which would be a single 32-bit pointer: the one item you specified.

Related

Memory usage of dynamic type in c#

Whether does dynamic-type use more memory size, than relevant type?
For example, does the field use only four bytes?
dynamic foo = (int) 1488;
Short answer:
No. It will actually use 12 bytes on 32bit machine and 24 bytes on 64 bit.
Long Answer
A dynamic type will be stored as an object but at run time the compiler will load many more bytes to make sense of what to do with the dynamic type. In order to do that, a lot more memory will be used to figure that out. Think of dynamic as a fancy object.
Here is a class:
class Mine
{
}
Here is the overhead for the above object on 32bit:
-------------------------- -4 bytes
| Object Header Word |
|------------------------| +0 bytes
| Method Table Pointer |
|------------------------| +4 bytes for Method Table Pointer
A total of 12 bytes needs to be allocated to it since the smallest reference type on 32bit is 12 bytes.
If we add one field to that class like this:
class Mine
{
public int Field = 1488;
}
It will still take 12 bytes because the overhead and the int field can fit in the 12 bytes.
If we add another int field, it will take 16 bytes.
However, if we add one dynamic field to that class like this:
class Mine
{
public dynamic Field = (int)1488;
}
It will NOT be 12 bytes. The dynamic field will be treated like an object and thus the size will be 12 + 12 = 24 bytes.
What is interesting is if you do this instead:
class Mine
{
public dynamic Field = (bool)false;
}
An instance of Mine will still take 24 bytes because even though the dynamic fields is only a boolean, it is still treated like an object.
On a 64bit machine, an instance of Mine with dynamic will take 48 bytes since the smallest reference type on 64 bit is 24 bytes (24 + 24 = 48 bytes).
Here are some gotchas you should be aware of and see this answer for size of object.

Why does WinDbg show System.Int32 variables as 24 bytes?

I'm analyzing a memory dump of a .NET process using WinDbg and I've noticed it reports the size of all System.Int32 variables on the heap as 24 bytes. Here's an example of a relevant DumpObj call on one of the variables:
0:000> !DumpObj /d 00000061c81c0e80
Name: System.Int32
MethodTable: 00007fff433f37c8
EEClass: 00007fff42e30130
Size: 24(0x18) bytes
File: C:\Windows\Microsoft.Net\assembly\GAC_64\mscorlib\v4.0_4.0.0.0__b77a5c561934e089\mscorlib.dll
Fields:
MT Field Offset Type VT Attr Value Name
00007fff433f37c8 4000456 8 System.Int32 1 instance 141 m_value
As far as I know, the size System.Int32 is supposed to be 4 bytes. What is the source of this discrepancy?
There's an overhead to any object on the heap. On 32-bit MS.NET runtime, this is 8 bytes, and on 64-bit, 16 bytes (disclaimer: this isn't strictly contractual and may change in the future or in a conforming implementation of a .NET runtime).
Since your int is boxed, it will have the 16-byte overhead. So you might expect 20 bytes to be used total. Well, except that on 64-bit systems, objects (and structures) are padded to 8-byte boundaries, so you actually get 24-bytes per int.
In contrast, when you use a struct with 16 integers, you'll only use 16 + 4 * 16 = 80 bytes of memory, for a total of 5 bytes per integer.
And again, most of this is an implementation detail, so not something you can rely on; it's perfectly possible for a valid .NET runtime to store a single int in 1 MiB of memory if it desires to do so, and it could also store it in some compact representation or with interning, as long as it conforms to all the contractual behaviour of the type. It's also quite simplified even compared to actual MS runtime implementations - for example, if your object gets large enough, it will need more overhead.
it is not size of int32 do dd or dq address and see your int32 stuck in the second dword or qword
there is an implicit overhead of 12 bytes or 24 bytes per object for x86 / x64 respectively
0:004> .shell -ci "!DumpObj /d 01c72360" grep -i size
Size: 12(0xc) bytes
.shell: Process exited
0:004> dd 01c72360 l4
01c72360 5890c770 000001b5 80000000 5890afb0
0:004> .shell -ci "!DumpObj /d 01c72360" grep -i method
MethodTable: 5890c770
.shell: Process exited
0:004> .shell -ci "!DumpObj /d 01c72360" grep -i value
MT Field Offset Type VT Attr Value Name
5890c770 400044f 4 System.Int32 1 instance 437 m_value
.shell: Process exited
0:004> ? 1b5
Evaluate expression: 437 = 000001b5
leaving int32 apart lets dissect a widestring "stream" in x86
actualsizereqdfor(L"stream\0") = 7 * sizeof(wchar_t) == 7 * 2 == 0n14;
sizeof(method table ) == 0n04;
sizeof(sizeof(L"stream)) == 0n04;
sizeof(padding ?? terminator ?? whatever ?? ) == 0n04;
so total size == 0n26
result of dumpobj
0:004> !DumpObj /d 01c73ad0
Name: System.String
MethodTable: 5890afb0
Size: 26(0x1a) bytes
String: stream
Fields:
MT Field Offset Type VT Attr Value Name
5890c770 40000aa 4 System.Int32 1 instance 6 m_stringLength
5890b9a8 40000ab 8 System.Char 1 instance 73 m_firstChar
5890afb0 40000ac c System.String 0 shared static Empty
raw display
0:004> db 01c73ad0 l1a
01c73ad0 b0 af 90 58 06 00 00 00-73 00 74 00 72 00 65 00 ...X....s.t.r.e.
01c73ae0 61 00 6d 00 00 00 00 00-00 00 a.m.......

Real size in memory of an array [duplicate]

I was trying to determine the overhead of the header on a .NET array (in a 32-bit process) using this code:
long bytes1 = GC.GetTotalMemory(false);
object[] array = new object[10000];
for (int i = 0; i < 10000; i++)
array[i] = new int[1];
long bytes2 = GC.GetTotalMemory(false);
array[0] = null; // ensure no garbage collection before this point
Console.WriteLine(bytes2 - bytes1);
// Calculate array overhead in bytes by subtracting the size of
// the array elements (40000 for object[10000] and 4 for each
// array), and dividing by the number of arrays (10001)
Console.WriteLine("Array overhead: {0:0.000}",
((double)(bytes2 - bytes1) - 40000) / 10001 - 4);
Console.Write("Press any key to continue...");
Console.ReadKey();
The result was
204800
Array overhead: 12.478
In a 32-bit process, object[1] should be the same size as int[1], but in fact the overhead jumps by 3.28 bytes to
237568
Array overhead: 15.755
Anyone know why?
(By the way, if anyone's curious, the overhead for non-array objects, e.g. (object)i in the loop above, is about 8 bytes (8.384). I heard it's 16 bytes in 64-bit processes.)
Here's a slightly neater (IMO) short but complete program to demonstrate the same thing:
using System;
class Test
{
const int Size = 100000;
static void Main()
{
object[] array = new object[Size];
long initialMemory = GC.GetTotalMemory(true);
for (int i = 0; i < Size; i++)
{
array[i] = new string[0];
}
long finalMemory = GC.GetTotalMemory(true);
GC.KeepAlive(array);
long total = finalMemory - initialMemory;
Console.WriteLine("Size of each element: {0:0.000} bytes",
((double)total) / Size);
}
}
But I get the same results - the overhead for any reference type array is 16 bytes, whereas the overhead for any value type array is 12 bytes. I'm still trying to work out why that is, with the help of the CLI spec. Don't forget that reference type arrays are covariant, which may be relevant...
EDIT: With the help of cordbg, I can confirm Brian's answer - the type pointer of a reference-type array is the same regardless of the actual element type. Presumably there's some funkiness in object.GetType() (which is non-virtual, remember) to account for this.
So, with code of:
object[] x = new object[1];
string[] y = new string[1];
int[] z = new int[1];
z[0] = 0x12345678;
lock(z) {}
We end up with something like the following:
Variables:
x=(0x1f228c8) <System.Object[]>
y=(0x1f228dc) <System.String[]>
z=(0x1f228f0) <System.Int32[]>
Memory:
0x1f228c4: 00000000 003284dc 00000001 00326d54 00000000 // Data for x
0x1f228d8: 00000000 003284dc 00000001 00329134 00000000 // Data for y
0x1f228ec: 00000000 00d443fc 00000001 12345678 // Data for z
Note that I've dumped the memory 1 word before the value of the variable itself.
For x and y, the values are:
The sync block, used for locking the hash code (or a thin lock - see Brian's comment)
Type pointer
Size of array
Element type pointer
Null reference (first element)
For z, the values are:
Sync block
Type pointer
Size of array
0x12345678 (first element)
Different value type arrays (byte[], int[] etc) end up with different type pointers, whereas all reference type arrays use the same type pointer, but have a different element type pointer. The element type pointer is the same value as you'd find as the type pointer for an object of that type. So if we looked at a string object's memory in the above run, it would have a type pointer of 0x00329134.
The word before the type pointer certainly has something to do with either the monitor or the hash code: calling GetHashCode() populates that bit of memory, and I believe the default object.GetHashCode() obtains a sync block to ensure hash code uniqueness for the lifetime of the object. However, just doing lock(x){} didn't do anything, which surprised me...
All of this is only valid for "vector" types, by the way - in the CLR, a "vector" type is a single-dimensional array with a lower-bound of 0. Other arrays will have a different layout - for one thing, they'd need the lower bound stored...
So far this has been experimentation, but here's the guesswork - the reason for the system being implemented the way it has. From here on, I really am just guessing.
All object[] arrays can share the same JIT code. They're going to behave the same way in terms of memory allocation, array access, Length property and (importantly) the layout of references for the GC. Compare that with value type arrays, where different value types may have different GC "footprints" (e.g. one might have a byte and then a reference, others will have no references at all, etc).
Every time you assign a value within an object[] the runtime needs to check that it's valid. It needs to check that the type of the object whose reference you're using for the new element value is compatible with the element type of the array. For instance:
object[] x = new object[1];
object[] y = new string[1];
x[0] = new object(); // Valid
y[0] = new object(); // Invalid - will throw an exception
This is the covariance I mentioned earlier. Now given that this is going to happen for every single assignment, it makes sense to reduce the number of indirections. In particular, I suspect you don't really want to blow the cache by having to go to the type object for each assigment to get the element type. I suspect (and my x86 assembly isn't good enough to verify this) that the test is something like:
Is the value to be copied a null reference? If so, that's fine. (Done.)
Fetch the type pointer of the object the reference points at.
Is that type pointer the same as the element type pointer (simple binary equality check)? If so, that's fine. (Done.)
Is that type pointer assignment-compatible with the element type pointer? (Much more complicated check, with inheritance and interfaces involved.) If so, that's fine - otherwise, throw an exception.
If we can terminate the search in the first three steps, there's not a lot of indirection - which is good for something that's going to happen as often as array assignments. None of this needs to happen for value type assignments, because that's statically verifiable.
So, that's why I believe reference type arrays are slightly bigger than value type arrays.
Great question - really interesting to delve into it :)
Array is a reference type. All reference types carry two additional word fields. The type reference and a SyncBlock index field, which among other things is used to implement locks in the CLR. So the type overhead on reference types is 8 bytes on 32 bit. On top of that the array itself also stores the length which is another 4 bytes. This brings the total overhead to 12 bytes.
And I just learned from Jon Skeet's answer, arrays of reference types has an additional 4 bytes overhead. This can be confirmed using WinDbg. It turns out that the additional word is another type reference for the type stored in the array. All arrays of reference types are stored internally as object[], with the additional reference to the type object of the actual type. So a string[] is really just an object[] with an additional type reference to the type string. For details please see below.
Values stored in arrays: Arrays of reference types hold references to objects, so each entry in the array is the size of a reference (i.e. 4 bytes on 32 bit). Arrays of value types store the values inline and thus each element will take up the size of the type in question.
This question may also be of interest: C# List<double> size vs double[] size
Gory Details
Consider the following code
var strings = new string[1];
var ints = new int[1];
strings[0] = "hello world";
ints[0] = 42;
Attaching WinDbg shows the following:
First let's take a look at the value type array.
0:000> !dumparray -details 017e2acc
Name: System.Int32[]
MethodTable: 63b9aa40
EEClass: 6395b4d4
Size: 16(0x10) bytes
Array: Rank 1, Number of elements 1, Type Int32
Element Methodtable: 63b9aaf0
[0] 017e2ad4
Name: System.Int32
MethodTable 63b9aaf0
EEClass: 6395b548
Size: 12(0xc) bytes
(C:\Windows\assembly\GAC_32\mscorlib\2.0.0.0__b77a5c561934e089\mscorlib.dll)
Fields:
MT Field Offset Type VT Attr Value Name
63b9aaf0 40003f0 0 System.Int32 1 instance 42 m_value <=== Our value
0:000> !objsize 017e2acc
sizeof(017e2acc) = 16 ( 0x10) bytes (System.Int32[])
0:000> dd 017e2acc -0x4
017e2ac8 00000000 63b9aa40 00000001 0000002a <=== That's the value
First we dump the array and the one element with value of 42. As can be seen the size is 16 bytes. That is 4 bytes for the int32 value itself, 8 bytes for regular reference type overhead and another 4 bytes for the length of the array.
The raw dump shows the SyncBlock, the method table for int[], the length, and the value of 42 (2a in hex). Notice that the SyncBlock is located just in front of the object reference.
Next, let's look at the string[] to find out what the additional word is used for.
0:000> !dumparray -details 017e2ab8
Name: System.String[]
MethodTable: 63b74ed0
EEClass: 6395a8a0
Size: 20(0x14) bytes
Array: Rank 1, Number of elements 1, Type CLASS
Element Methodtable: 63b988a4
[0] 017e2a90
Name: System.String
MethodTable: 63b988a4
EEClass: 6395a498
Size: 40(0x28) bytes <=== Size of the string
(C:\Windows\assembly\GAC_32\mscorlib\2.0.0.0__b77a5c561934e089\mscorlib.dll)
String: hello world
Fields:
MT Field Offset Type VT Attr Value Name
63b9aaf0 4000096 4 System.Int32 1 instance 12 m_arrayLength
63b9aaf0 4000097 8 System.Int32 1 instance 11 m_stringLength
63b99584 4000098 c System.Char 1 instance 68 m_firstChar
63b988a4 4000099 10 System.String 0 shared static Empty
>> Domain:Value 00226438:017e1198 <<
63b994d4 400009a 14 System.Char[] 0 shared static WhitespaceChars
>> Domain:Value 00226438:017e1760 <<
0:000> !objsize 017e2ab8
sizeof(017e2ab8) = 60 ( 0x3c) bytes (System.Object[]) <=== Notice the underlying type of the string[]
0:000> dd 017e2ab8 -0x4
017e2ab4 00000000 63b74ed0 00000001 63b988a4 <=== Method table for string
017e2ac4 017e2a90 <=== Address of the string in memory
0:000> !dumpmt 63b988a4
EEClass: 6395a498
Module: 63931000
Name: System.String
mdToken: 02000024 (C:\Windows\assembly\GAC_32\mscorlib\2.0.0.0__b77a5c561934e089\mscorlib.dll)
BaseSize: 0x10
ComponentSize: 0x2
Number of IFaces in IFaceMap: 7
Slots in VTable: 196
First we dump the array and the string. Next we dump the size of the string[]. Notice that WinDbg lists the type as System.Object[] here. The object size in this case includes the string itself, so the total size is the 20 from the array plus the 40 for the string.
By dumping the raw bytes of the instance we can see the following: First we have the SyncBlock, then follows the method table for object[], then the length of the array. After that we find the additional 4 bytes with the reference to the method table for string. This can be verified by the dumpmt command as shown above. Finally we find the single reference to the actual string instance.
In conclusion
The overhead for arrays can be broken down as follows (on 32 bit that is)
4 bytes SyncBlock
4 bytes for Method table (type reference) for the array itself
4 bytes for Length of array
Arrays of reference types adds another 4 bytes to hold the method table of the actual element type (reference type arrays are object[] under the hood)
I.e. the overhead is 12 bytes for value type arrays and 16 bytes for reference type arrays.
I think you are making some faulty assumptions while measuring, as the memory allocation (via GetTotalMemory) during your loop may be different than the actual required memory for just the arrays - the memory may be allocated in larger blocks, there may be other objects in memory that are reclaimed during the loop, etc.
Here's some info for you on array overhead:
Arrays Undocumented
Article by Jeffrey Richter
.Net Type Internals
Because heap management (since you deal with GetTotalMemory) can only allocate rather large blocks, which latter are allocated by smaller chunks for programmer purposes by CLR.
I'm sorry for the offtopic but I found interesting info on memory overheading just today morning.
We have a project which operates huge amount of data (up to 2GB). As the major storage we use Dictionary<T,T>. Thousands of dictionaries are created actually. After change it to List<T> for keys and List<T> for values (we implemented IDictionary<T,T> ourselves) the memory usage decreased on about 30-40%.
Why?

Why does struct alignment depend on whether a field type is primitive or user-defined?

In Noda Time v2, we're moving to nanosecond resolution. That means we can no longer use an 8-byte integer to represent the whole range of time we're interested in. That has prompted me to investigate the memory usage of the (many) structs of Noda Time, which has in turn led me to uncover a slight oddity in the CLR's alignment decision.
Firstly, I realize that this is an implementation decision, and that the default behaviour could change at any time. I realize that I can modify it using [StructLayout] and [FieldOffset], but I'd rather come up with a solution which didn't require that if possible.
My core scenario is that I have a struct which contains a reference-type field and two other value-type fields, where those fields are simple wrappers for int. I had hoped that that would be represented as 16 bytes on the 64-bit CLR (8 for the reference and 4 for each of the others), but for some reason it's using 24 bytes. I'm measuring the space using arrays, by the way - I understand that the layout may be different in different situations, but this felt like a reasonable starting point.
Here's a sample program demonstrating the issue:
using System;
using System.Runtime.InteropServices;
#pragma warning disable 0169
struct Int32Wrapper
{
int x;
}
struct TwoInt32s
{
int x, y;
}
struct TwoInt32Wrappers
{
Int32Wrapper x, y;
}
struct RefAndTwoInt32s
{
string text;
int x, y;
}
struct RefAndTwoInt32Wrappers
{
string text;
Int32Wrapper x, y;
}
class Test
{
static void Main()
{
Console.WriteLine("Environment: CLR {0} on {1} ({2})",
Environment.Version,
Environment.OSVersion,
Environment.Is64BitProcess ? "64 bit" : "32 bit");
ShowSize<Int32Wrapper>();
ShowSize<TwoInt32s>();
ShowSize<TwoInt32Wrappers>();
ShowSize<RefAndTwoInt32s>();
ShowSize<RefAndTwoInt32Wrappers>();
}
static void ShowSize<T>()
{
long before = GC.GetTotalMemory(true);
T[] array = new T[100000];
long after = GC.GetTotalMemory(true);
Console.WriteLine("{0}: {1}", typeof(T),
(after - before) / array.Length);
}
}
And the compilation and output on my laptop:
c:\Users\Jon\Test>csc /debug- /o+ ShowMemory.cs
Microsoft (R) Visual C# Compiler version 12.0.30501.0
for C# 5
Copyright (C) Microsoft Corporation. All rights reserved.
c:\Users\Jon\Test>ShowMemory.exe
Environment: CLR 4.0.30319.34014 on Microsoft Windows NT 6.2.9200.0 (64 bit)
Int32Wrapper: 4
TwoInt32s: 8
TwoInt32Wrappers: 8
RefAndTwoInt32s: 16
RefAndTwoInt32Wrappers: 24
So:
If you don't have a reference type field, the CLR is happy to pack Int32Wrapper fields together (TwoInt32Wrappers has a size of 8)
Even with a reference type field, the CLR is still happy to pack int fields together (RefAndTwoInt32s has a size of 16)
Combining the two, each Int32Wrapper field appears to be padded/aligned to 8 bytes. (RefAndTwoInt32Wrappers has a size of 24.)
Running the same code in the debugger (but still a release build) shows a size of 12.
A few other experiments have yielded similar results:
Putting the reference type field after the value type fields doesn't help
Using object instead of string doesn't help (I expect it's "any reference type")
Using another struct as a "wrapper" around the reference doesn't help
Using a generic struct as a wrapper around the reference doesn't help
If I keep adding fields (in pairs for simplicity), int fields still count for 4 bytes, and Int32Wrapper fields count for 8 bytes
Adding [StructLayout(LayoutKind.Sequential, Pack = 4)] to every struct in sight doesn't change the results
Does anyone have any explanation for this (ideally with reference documentation) or a suggestion of how I can get hint to the CLR that I'd like the fields to be packed without specifying a constant field offset?
I think this is a bug. You are seeing the side-effect of automatic layout, it likes to align non-trivial fields to an address that's a multiple of 8 bytes in 64-bit mode. It occurs even when you explicitly apply the [StructLayout(LayoutKind.Sequential)] attribute. That is not supposed to happen.
You can see it by making the struct members public and appending test code like this:
var test = new RefAndTwoInt32Wrappers();
test.text = "adsf";
test.x.x = 0x11111111;
test.y.x = 0x22222222;
Console.ReadLine(); // <=== Breakpoint here
When the breakpoint hits, use Debug + Windows + Memory + Memory 1. Switch to 4-byte integers and put &test in the Address field:
0x000000E928B5DE98 0ed750e0 000000e9 11111111 00000000 22222222 00000000
0xe90ed750e0 is the string pointer on my machine (not yours). You can easily see the Int32Wrappers, with the extra 4 bytes of padding that turned the size into 24 bytes. Go back to the struct and put the string last. Repeat and you'll see the string pointer is still first. Violating LayoutKind.Sequential, you got LayoutKind.Auto.
It is going to be difficult to convince Microsoft to fix this, it has worked this way for too long so any change is going to be breaking something. The CLR only makes an attempt to honor [StructLayout] for the managed version of a struct and make it blittable, it in general quickly gives up. Notoriously for any struct that contains a DateTime. You only get the true LayoutKind guarantee when marshaling a struct. The marshaled version certainly is 16 bytes, as Marshal.SizeOf() will tell you.
Using LayoutKind.Explicit fixes it, not what you wanted to hear.
EDIT2
struct RefAndTwoInt32Wrappers
{
public int x;
public string s;
}
This code will be 8 byte aligned so the struct will have 16 bytes. By comparison this:
struct RefAndTwoInt32Wrappers
{
public int x,y;
public string s;
}
Will be 4 byte aligned so this struct also will have 16 bytes. So the rationale here is that struct aligment in CLR is determined by the number of most aligned fields, clases obviously cannot do that so they will remain 8 byte aligned.
Now if we combine all that and create struct:
struct RefAndTwoInt32Wrappers
{
public int x,y;
public Int32Wrapper z;
public string s;
}
It will have 24 bytes {x,y} will have 4 bytes each and {z,s} will have 8 bytes. Once we introduce a ref type in the struct CLR will always align our custom struct to match the class alignment.
struct RefAndTwoInt32Wrappers
{
public Int32Wrapper z;
public long l;
public int x,y;
}
This code will have 24 bytes since Int32Wrapper will be aligned the same as long. So the custom struct wrapper will always align to the highest/best aligned field in the structure or to it's own internal most significant fields. So in the case of a ref string that is 8 byte aligned the struct wrapper will align to that.
Concluding custom struct field inside struct will always be aligned to the highest aligned instance field in the structure. Now if i'm not sure if this is a bug but without some evidence I'm going to stick by my opinion that this might be conscious decision.
EDIT
The sizes are actually accurate only when allocated on a heap but the structs themselves have smaller sizes (the exact sizes of it's fields). Further analysis seam to suggest that this might be a bug in the CLR code, but needs to be backed up by evidence.
I will inspect cli code and post further updates if something useful will be found.
This is a alignment strategy used by .NET mem allocator.
public static RefAndTwoInt32s[] test = new RefAndTwoInt32s[1];
static void Main()
{
test[0].text = "a";
test[0].x = 1;
test[0].x = 1;
Console.ReadKey();
}
This code compiled with .net40 under x64, In WinDbg lets do the following:
Lets find the type on the Heap first:
0:004> !dumpheap -type Ref
Address MT Size
0000000003e72c78 000007fe61e8fb58 56
0000000003e72d08 000007fe039d3b78 40
Statistics:
MT Count TotalSize Class Name
000007fe039d3b78 1 40 RefAndTwoInt32s[]
000007fe61e8fb58 1 56 System.Reflection.RuntimeAssembly
Total 2 objects
Once we have it lets see what's under that address:
0:004> !do 0000000003e72d08
Name: RefAndTwoInt32s[]
MethodTable: 000007fe039d3b78
EEClass: 000007fe039d3ad0
Size: 40(0x28) bytes
Array: Rank 1, Number of elements 1, Type VALUETYPE
Fields:
None
We see that this is a ValueType and its the one we created. Since this is an array we need to get the ValueType def of a single element in the array:
0:004> !dumparray -details 0000000003e72d08
Name: RefAndTwoInt32s[]
MethodTable: 000007fe039d3b78
EEClass: 000007fe039d3ad0
Size: 40(0x28) bytes
Array: Rank 1, Number of elements 1, Type VALUETYPE
Element Methodtable: 000007fe039d3a58
[0] 0000000003e72d18
Name: RefAndTwoInt32s
MethodTable: 000007fe039d3a58
EEClass: 000007fe03ae2338
Size: 32(0x20) bytes
File: C:\ConsoleApplication8\bin\Release\ConsoleApplication8.exe
Fields:
MT Field Offset Type VT Attr Value Name
000007fe61e8c358 4000006 0 System.String 0 instance 0000000003e72d30 text
000007fe61e8f108 4000007 8 System.Int32 1 instance 1 x
000007fe61e8f108 4000008 c System.Int32 1 instance 0 y
The structure is actually 32 bytes since it's 16 bytes is reserved for padding so in actuality every structure is at least 16 bytes in size from the get go.
if you add 16 bytes from ints and a string ref to: 0000000003e72d18 + 8 bytes EE/padding you will end up at 0000000003e72d30 and this is the staring point for string reference, and since all references are 8 byte padded from their first actual data field this makes up for our 32 bytes for this structure.
Let's see if the string is actually padded that way:
0:004> !do 0000000003e72d30
Name: System.String
MethodTable: 000007fe61e8c358
EEClass: 000007fe617f3720
Size: 28(0x1c) bytes
File: C:\WINDOWS\Microsoft.Net\assembly\GAC_64\mscorlib\v4.0_4.0.0.0__b77a5c561934e089\mscorlib.dll
String: a
Fields:
MT Field Offset Type VT Attr Value Name
000007fe61e8f108 40000aa 8 System.Int32 1 instance 1 m_stringLength
000007fe61e8d640 40000ab c System.Char 1 instance 61 m_firstChar
000007fe61e8c358 40000ac 18 System.String 0 shared static Empty
>> Domain:Value 0000000001577e90:NotInit <<
Now lets analyse the above program the same way:
public static RefAndTwoInt32Wrappers[] test = new RefAndTwoInt32Wrappers[1];
static void Main()
{
test[0].text = "a";
test[0].x.x = 1;
test[0].y.x = 1;
Console.ReadKey();
}
0:004> !dumpheap -type Ref
Address MT Size
0000000003c22c78 000007fe61e8fb58 56
0000000003c22d08 000007fe039d3c00 48
Statistics:
MT Count TotalSize Class Name
000007fe039d3c00 1 48 RefAndTwoInt32Wrappers[]
000007fe61e8fb58 1 56 System.Reflection.RuntimeAssembly
Total 2 objects
Our struct is 48 bytes now.
0:004> !dumparray -details 0000000003c22d08
Name: RefAndTwoInt32Wrappers[]
MethodTable: 000007fe039d3c00
EEClass: 000007fe039d3b58
Size: 48(0x30) bytes
Array: Rank 1, Number of elements 1, Type VALUETYPE
Element Methodtable: 000007fe039d3ae0
[0] 0000000003c22d18
Name: RefAndTwoInt32Wrappers
MethodTable: 000007fe039d3ae0
EEClass: 000007fe03ae2338
Size: 40(0x28) bytes
File: C:\ConsoleApplication8\bin\Release\ConsoleApplication8.exe
Fields:
MT Field Offset Type VT Attr Value Name
000007fe61e8c358 4000009 0 System.String 0 instance 0000000003c22d38 text
000007fe039d3a20 400000a 8 Int32Wrapper 1 instance 0000000003c22d20 x
000007fe039d3a20 400000b 10 Int32Wrapper 1 instance 0000000003c22d28 y
Here the situation is the same, if we add to 0000000003c22d18 + 8 bytes of string ref we will end up at the start of the first Int wrapper where the value actually point to the address we are at.
Now we can see that each value is an object reference again lets confirm that by peeking 0000000003c22d20.
0:004> !do 0000000003c22d20
<Note: this object has an invalid CLASS field>
Invalid object
Actually thats correct since its a struct the address tells us nothing if this is an obj or vt.
0:004> !dumpvc 000007fe039d3a20 0000000003c22d20
Name: Int32Wrapper
MethodTable: 000007fe039d3a20
EEClass: 000007fe03ae23c8
Size: 24(0x18) bytes
File: C:\ConsoleApplication8\bin\Release\ConsoleApplication8.exe
Fields:
MT Field Offset Type VT Attr Value Name
000007fe61e8f108 4000001 0 System.Int32 1 instance 1 x
So in actuality this is a more like an Union type that will get 8 byte aligned this time around (all of the paddings will be aligned with the parent struct). If it weren't then we would end up with 20 bytes and that's not optimal so the mem allocator will never allow it to happen. If you do the math again it will turn out that the struct is indeed 40 bytes of size.
So if you want to be more conservative with memory you should never pack it in a struct custom struct type but instead use simple arrays. Another way is to allocate memory off heap (VirtualAllocEx for e.g)
this way you are given you own memory block and you manage it the way you want.
The final question here is why all of a sudden we might get layout like that. Well if you compare the jited code and performance of a int[] incrementation with struct[] with a counter field incrementation the second one will generate a 8 byte aligned address being an union, but when jited this translates to more optimized assembly code (singe LEA vs multiple MOV). However in the case described here the performance will be actually worse so my take is that this is consistent with the underlying CLR implementation since it's a custom type that can have multiple fields so it may be easier/better to put the starting address instead of a value (since it would be impossible) and do struct padding there, thus resulting in bigger byte size.
Summary see #Hans Passant's answer probably above. Layout Sequential doesn't work
Some testing:
It is definitely only on 64bit and the object reference "poisons" the struct. 32 bit does what you are expecting:
Environment: CLR 4.0.30319.34209 on Microsoft Windows NT 6.2.9200.0 (32 bit)
ConsoleApplication1.Int32Wrapper: 4
ConsoleApplication1.TwoInt32s: 8
ConsoleApplication1.TwoInt32Wrappers: 8
ConsoleApplication1.ThreeInt32Wrappers: 12
ConsoleApplication1.Ref: 4
ConsoleApplication1.RefAndTwoInt32s: 12
ConsoleApplication1.RefAndTwoInt32Wrappers: 12
ConsoleApplication1.RefAndThreeInt32s: 16
ConsoleApplication1.RefAndThreeInt32Wrappers: 16
As soon as the object reference is added all the structs expand to be 8 bytes rather their 4 byte size. Expanding the tests:
Environment: CLR 4.0.30319.34209 on Microsoft Windows NT 6.2.9200.0 (64 bit)
ConsoleApplication1.Int32Wrapper: 4
ConsoleApplication1.TwoInt32s: 8
ConsoleApplication1.TwoInt32Wrappers: 8
ConsoleApplication1.ThreeInt32Wrappers: 12
ConsoleApplication1.Ref: 8
ConsoleApplication1.RefAndTwoInt32s: 16
ConsoleApplication1.RefAndTwoInt32sSequential: 16
ConsoleApplication1.RefAndTwoInt32Wrappers: 24
ConsoleApplication1.RefAndThreeInt32s: 24
ConsoleApplication1.RefAndThreeInt32Wrappers: 32
ConsoleApplication1.RefAndFourInt32s: 24
ConsoleApplication1.RefAndFourInt32Wrappers: 40
As you can see as soon as the reference is added every Int32Wrapper becomes 8 bytes so isn't simple alignment. I shrunk down the array allocation incase it was LoH allocation which is differently aligned.
Just to add some data to the mix - I created one more type from the ones you had:
struct RefAndTwoInt32Wrappers2
{
string text;
TwoInt32Wrappers z;
}
The program writes out:
RefAndTwoInt32Wrappers2: 16
So it looks like the TwoInt32Wrappers struct aligns properly in the new RefAndTwoInt32Wrappers2 struct.

Using DLLImport to import an Object

I have a dll for a c++ class (SLABHIDDevice.dll). I am trying to use the functions of this dll in a C#.net application. The dll contains several methods which I can use easily with statements such as this...
(I appolagize if i get some of the terminology wrong here I am new to using dlls)
[DllImport("SLABHIDDevice.dll")]
public static extern byte GetHidString
(Int32 deviceIndex, Int32 vid, Int32 pid,
Byte hidStringType, String deviceString,
Int32 deviceStringLength);
The documentation for SLABHIDDevice.dll says that it also contains a class object, CHIDDevice.
and that object has a whole list of member functions such as Open();
If I try to import Open() using the same syntax as above, I get an error saying that it can not find an entry point for the Open() function. Is this because Open() is a member of CHIDDevice?
This is the makeup of the dll from DUMPBIN... The bottom three functions are the only ones I am able to get to work? Does anyone know what syntax I need to use to get the other ones? What do the question marks mean that precede the function names?
Dump of file SLABHIDDEVICE.dll
File Type: DLL
Section contains the following exports for SLABHIDDevice.dll
00000000 characteristics
47E13E0F time date stamp Wed Mar 19 12:23:43 2008
0.00 version
1 ordinal base
26 number of functions
26 number of names
ordinal hint RVA name
4 0 00001000 ??0CHIDDevice##QAE#ABV0##Z
5 1 00001330 ??0CHIDDevice##QAE#XZ
6 2 00001430 ??1CHIDDevice##UAE#XZ
7 3 00001080 ??4CHIDDevice##QAEAAV0#ABV0##Z
8 4 00020044 ??_7CHIDDevice##6B#
9 5 00001460 ?Close#CHIDDevice##QAEEXZ
10 6 00001C70 ?FlushBuffers#CHIDDevice##QAEHXZ
11 7 00001CA0 ?GetFeatureReportBufferLength#CHIDDevice##QAEGXZ
12 8 00001850 ?GetFeatureReport_Control#CHIDDevice##QAEEPAEK#Z
13 9 00001C80 ?GetInputReportBufferLength#CHIDDevice##QAEGXZ
14 A 00001BE0 ?GetInputReport_Control#CHIDDevice##QAEEPAEK#Z
15 B 00001A20 ?GetInputReport_Interrupt#CHIDDevice##QAEEPAEKGPAK#Z
16 C 00001CB0 ?GetMaxReportRequest#CHIDDevice##QAEKXZ
17 D 00001C90 ?GetOutputReportBufferLength#CHIDDevice##QAEGXZ
18 E 00001730 ?GetString#CHIDDevice##QAEEEPADK#Z
19 F 00001CC0 ?GetTimeouts#CHIDDevice##QAEXPAI0#Z
20 10 00001700 ?IsOpened#CHIDDevice##QAEHXZ
21 11 000014A0 ?Open#CHIDDevice##QAEEKGGG#Z
22 12 00001360 ?ResetDeviceData#CHIDDevice##AAEXXZ
23 13 00001810 ?SetFeatureReport_Control#CHIDDevice##QAEEPAEK#Z
24 14 00001B80 ?SetOutputReport_Control#CHIDDevice##QAEEPAEK#Z
25 15 000018C0 ?SetOutputReport_Interrupt#CHIDDevice##QAEEPAEK#Z
26 16 00001CE0 ?SetTimeouts#CHIDDevice##QAEXII#Z
3 17 00001320 GetHidGuid
2 18 00001230 GetHidString
1 19 00001190 GetNumHidDevices
Summary
6000 .data
7000 .rdata
5000 .reloc
4000 .rsrc
1C000 .text
You cannot use P/Invoke to call instance methods of a C++ class. The primary hang-up is that you can't create an object of the class, you cannot discover the required memory allocation size. Passing the implicit "this" pointer to the instance method is another problem, it needs to be passed in a register.
You'll need to create a managed wrapper for the class, that requires using the C++/CLI language. Google "C++/CLI wrapper" for good hits.
C++ uses name mangling. All the weird symbols around your function names are a way for the compiler/linker to know the calling convention, parameters, return type, etc.
If you don't want to use name mangling with your functions, you need to surround them with a
extern "C" {
}
statement.
See http://en.wikipedia.org/wiki/Name_mangling

Categories

Resources