How many objects of which types are pinned during nursery collections?
1: mono$target:::gc-obj-pinned /arg4==0/ {
2: @[strjoin (copyinstr (arg2), strjoin (".", copyinstr (arg3)))] = count ();
3: }
This is very similar to the preceding script, but we use the pinned object probe and a guard to select for the nursery, as opposed to the whole heap.
...
Microsoft.Scripting.Actions.DynamicSiteTarget`3 863
IronPython.Runtime.Calls.CallTarget1 864
System.Object[] 1271
Microsoft.Scripting.Actions.CallSite`1 1350
System.String[] 1440
Here we see the pin counts for the 5 most pinned classes during nursery collections. Combining all nursery collections, for example, 1440 string arrays were pinned.
What if we’d like to know how many bytes of memory are pinned in the nursery during each collection?
1: mono$target:::gc-begin /arg0==0/ {
2: bytes = 0;
3: }
4:
5: mono$target:::gc-obj-pinned /arg4==0/ {
6: bytes += arg1;
7: }
8:
9: mono$target:::gc-end /arg0==0/ {
10: @ = quantize (bytes);
11: }
All we have to do is add up the sizes of all pinned objects for nursery collections.
value ------------- Distribution ------------- count
32 | 0
64 | 1
128 |@ 2
256 | 0
512 | 0
1024 | 0
2048 | 0
4096 |@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ 95
8192 | 0
This looks a bit suspicious. We had about 100 nursery collections, so according to the object counts above there were on average 13 object arrays and 14 string arrays pinned per nursery collection, in addition to lots of other objects. All of those supposedly amount to only about 4 kilobytes of memory. Are we counting something wrong or are these very short arrays? Let’s see:
1: mono$target:::gc-obj-pinned /arg4==0/ {
2: @[strjoin (copyinstr (arg2), strjoin (".", copyinstr (arg3)))] = quantize (arg1);
3: }
This gathers object size histograms per class for all objects pinned in the nursery. These are the parts of the output giving the counts for the arrays we’re interested in:
System.String[]
value ------------- Distribution ------------- count
8 | 0
16 |@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ 1344
32 |@@@ 96
64 | 0
System.Object[]
value ------------- Distribution ------------- count
8 | 0
16 |@@@@@@@@@@@@@@@@@@@@@@@@@@@ 870
32 |@@@@@@@@@@@@@ 409
64 | 0
Apparently those arrays really are very short, all around 16 to 32 bytes long. But maybe the probe is lying to us about the object size? If we know about Mono’s internal object layout we can read out the array length directly from the heap:
1: mono$target:::gc-obj-pinned /arg4 == 0 && copyinstr (arg3) == "Object[]"/ {
2: this->l = *(int*)copyin (arg0 + 12, 4);
3: @[arg1] = lquantize (this->l, 0, 8);
4: }
On 32-bit Mono the length of an array is at offset 12, so we read 4 bytes from there and cast it to an int
. We get a few graphs like these:
16
value ------------- Distribution ------------- count
< 0 | 0
0 |@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ 96
1 | 0
28
value ------------- Distribution ------------- count
2 | 0
3 |@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ 2
4 | 0
20
value ------------- Distribution ------------- count
0 | 0
1 |@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ 481
2 | 0
Each of them gives us the distribution of the array lengths for array objects of specific sizes. If everything is correct then for each array object size there must only be one array length, which is exactly what we get. For example, all 481 array objects of size 20 bytes have a length of one.
In fact, the numbers show that the sizes we get are exactly what we should expect given Mono’s object layout on 32-bits: A reference array with a given length has a size of 16 + 4*n
bytes.