Failed allocation - Cerb/STM32F4Discovery C# StringBuilder

I am using the Cerb .NETMF on an STM32F4 Discovery board.
I have a program that allocates a StringBuilder object with an initial capacity of 2048. In my debug, I get a line that reads:

Failed allocation for 1367 blocks, 16404 bytes

My deployment was originally 90K+, so I cut out some unnecessary references and shaved it down to 26880 bytes. I though surely cutting out 65K+ of assemblies would give me plenty of memory for a 16K allocation, but was surprised to see the same failed allocation message. The program completes successfully without an out of memory exception, so obviously it is still managing to allocate the object anyway.

My questions are:
Why was 16K being allocated? The StringBuilder object isn’t significantly larger than the initial capacity, is it?
How does the memory get allocated? Piecemeal? Do I need to look at a map?:slight_smile:
Should I avoid large buffers and thereby avoid performance hits from allocation failures?

The app is a quick and dirty one to output random bytes to the debug window for analysis, but is the seed of what will be a device function to provide random numbers for gaming.

@ Gregg, I am no expert at this yet :), so take what I say here with a pinch of salt until so one more experienced with the details on .NET MF ports chimes in. However, my understanding is the following

Your deployment is loaded into flash memory, while memory allocations come from the available RAM. Therefore reducing your deployment size would have minimal impact, I say minimal rather than nothing since it is feasible that some references you have might have actually also allocated memory from the RAM if you touched any static members.

.NET MF manages characters as UTF-8 internally and surfaces those as Unicode, therefore a char data type will be the size of an int. Now I have not inspected the internals sufficiently (yet) to understand how they are estimating the UTF-8 buffer sizes since UTF-8 code points can range from 1-5 (typically 1-3) bytes, ORACLE database for example uses an average of 3 bytes per char when using UTF-8, what .NET MF is doing exactly will be what I investigate today :slight_smile:

The following needs verification:
It is therefore feasible that the internally allocated char buffer will consume anywhere up to 3 times more RAM than “expected”.

What you probably need to look at is reducing your memory allocations rather than references. Do you have any other buffers that you are allocating that you might be able to remove temporarily to test further?

Just a quick update, I have looked at the 4.2 CLR code and it seems that char arrays are allocated as 2 bytes per char. So it seems it is only strings that are treated as UTF-8 internally, chars looks like they are Unicode all the way.

The type system lookup defines the char data type as being 16 bits, 2 bytes
TypeSystemLookup.cpp : Line 67

Array allocation code is in CLR_RT_HeapBlock_Array.cpp which calls ExtractHeapBlocksForArray. ExtractHeapBlocksForArray uses size from the Type System Look up to calculate the memory requirement for the array
Execution.cpp : Lines 1468 - 1496

So the StringBuilder would have an internal array of 4096 bytes + the overhead for the array data sizeof(CLR_RT_HeapBlock_Array). No where near your 16K, are you sure you do not have any other large buffers that are being allocated?

Yep. I allocate a 1024 uint array and pass that back as a result to the method using the StringBuilder. That’s it for large allocations, the rest is a few registers and local vars. The program is stripped to the essentials.

I would try 2 things, one at a time

  1. Run a GC before allocating the arrays
  2. Try halve the size of the arrays and read the data in chunks with multiple calls to see if there is relief or the problem occurs after a few calls, if it does then include the test from step 1.

Based on the results, maybe something becomes apparent.

1 Like

Thanks, taylorza! I didn’t find it directly, but you got me looking in the right place. Halving the array did not do it, nor did GC, but I found the issue. I was initially allocating 2048 characters in the StringBuilder, but my data was 1024 uint’s in hex, 8 characters per int. Allocating the StringBuilder with (sample size) x 8 relieved the issue! Obviously then, the allocation error came on resizing the StringBuilder, not the initial allocation. Thanks for the sounding wall, I really needed it.

1 Like

@ Gregg - Excellent. And thanks to your issue I learned more about how .NET MF works internally as well. A win all round I would say!