Mistake on this page?
Report an issue in GitHub or email us

Memory optimizations

Beginning with Mbed OS 5, new features such as RTOS created an increase in flash and RAM usage. This guide explains how to visualize the memory model in Mbed OS and how to instrument and optimize program memory use for release builds using Mbed OS 5.

Note: More information about the memory usage differences between Mbed OS 2 and Mbed OS 5 is available in a blog post.

Runtime memory tracing

Running out of memory is a common problem with resource constrained systems such as the MCUs on which Arm Mbed OS runs. When faced with an out of memory error, you often need to understand how your software uses dynamic memory. The runtime memory tracer in Mbed OS 5 is the tool that shows the runtime memory allocation patterns of your software: which parts of the code allocate and free memory and how much memory they need.

Using the memory tracer

The memory tracer is not enabled by default. To enable it, you need to enable the memory-tracing-enabled setting in the Mbed OS platform configuration options. We recommend doing this by adding it to your mbed_app.json:

{
    "target_overrides": {
        "*": {
            "platform.memory-tracing-enabled": true
        }
    }
}

Tip: See the documentation of the Arm Mbed configuration system for more details about mbed_app.json.

After it is enabled, the memory tracer intercepts the calls to the standard allocation functions (malloc, realloc, calloc and free). It invokes a user supplied callback each time one of these functions is called. To let the tracer know which callback it needs to invoke, call mbed_mem_trace_set_callback(callback_function_name) as early as possible (preferably at the beginning of your main function). You can find the full documentation of the callback function in the memory tracer header file. The tracer supplies a default callback function (mbed_mem_trace_default_callback) that outputs trace data on the Mbed console (using printf). For each memory operation, the callback outputs a line that begins with #<op>:<0xresult>;<0xcaller>-:

  • op identifies the memory operation (m for malloc, r for realloc, c for calloc and f for free).
  • result (base 16) is the result returned by the memory operation. This is always 0 for free because free doesn't return anything.
  • caller (base 16) is the address in the code where the memory operation was called.

The rest of the output depends on the operation being traced:

  • For malloc: size, where size is the original argument to malloc.
  • For realloc: 0xptr;size, where ptr (base 16) and size are the original arguments to realloc.
  • For calloc: nmemb;size, where nmemb and size are the original arguments to calloc.
  • For free: 0xptr, where ptr (base 16) is the original argument to free.

Examples:

  • #m:0x20003240;0x600d-50 encodes a malloc that returned 0x20003240. It was called by the instruction at 0x600D with the size argument equal to 50.
  • #f:0x0;0x602f-0x20003240 encodes a free that was called by the instruction at 0x602f with the ptr argument equal to 0x20003240.

Find the source of the default callback here. Besides being useful in itself, it can also serve as a template for user defined callback functions.

Tip: Find the full documentation of the callback function in the memory tracer header file.

Example

A simple code example that uses the memory tracer on a K64F board:

#include <stdlib.h>
#include "mbed.h"
#include "mbed_mem_trace.h"


int main() {
    mbed_mem_trace_set_callback(mbed_mem_trace_default_callback);
    while (true) {
        void *p = malloc(50);
        wait(0.5);
        free(p);
    }
}

It outputs the following trace:

#m:0x20003080;0x182f-50
#f:0x0;0x183f-0x20003080
#m:0x20003080;0x182f-50
#f:0x0;0x183f-0x20003080
#m:0x20003080;0x182f-50
#f:0x0;0x183f-0x20003080
...

Limitations

  • The tracer doesn't handle nested calls of the memory functions. For example, if you call realloc and the implementation of realloc calls malloc internally, the call to malloc is not traced.
  • The caller argument of the callback function isn't always reliable. It doesn't work at all on some toolchains, and it might output erroneous data on others.

Runtime memory statistics

Arm Mbed OS 5 provides various runtime statistics to help characterize resource usage. This allows easy identification of potential problems, such as a stack close to overflowing. The metrics currently supported are available for the heap and the stack.

Heap statistics

Heap statistics provide exact information about the number of bytes dynamically allocated by a program. It does not take into account heap fragmentation or allocation overhead. This allows allocation size reports to remain consistent, regardless of order of allocation (fragmentation) or allocation algorithm (overhead).

To enable heap stats from Mbed CLI:

  1. Add the command-line flag -DMBED_HEAP_STATS_ENABLED=1, for example mbed compile -DMBED_HEAP_STATS_ENABLED=1
  2. Use the function mbed_stats_heap_get() to take a snapshot of heap stats.

To enable heap stats using mbed_app.json:

  1. Add the following to mbed_app.json:

    {
        "macros": [
            "MBED_HEAP_STATS_ENABLED=1"
        ],
        ...
    }
    
  2. Use the function mbed_stats_heap_get() to take a snapshot of heap stats.

    Note: This function is available even when the heap stats are not enabled but always returns zero for all fields.

    mbed_stats_heap_get() returns a struct containing the following:

    Variable Description
    current_size Bytes allocated currently.
    max_size Max bytes allocated at a given time.
    total_size Cumulative sum of bytes ever allocated.
    reserved_size Current number of bytes allocated for the heap.
    alloc_cnt Current number of allocations.
    alloc_fail_cnt Number of failed allocations.
    overhead_size Overhead added to heap for stats.

Example heap statistics use cases

  • Getting worst case memory usage, max_size, to properly size MCU RAM.
  • Detecting program memory leaks by the current size allocated (current_size) or number of allocations in use (alloc_cnt).
  • Use alloc_fail_cnt to check if allocations have been failing, and if so, how many.

Example program using heap statistics

#include "mbed.h"
#include "mbed_stats.h"

int main(void)
{
    mbed_stats_heap_t heap_stats;

    printf("Starting heap stats example\r\n");
    mbed_stats_heap_get(&heap_stats);
    printf("Start; Current heap: %lu\n", heap_stats.current_size);
    printf("Start; Max heap size: %lu\n", heap_stats.max_size);

    printf("\nAllocating 1000 bytes\n");
    void *allocation = malloc(1000);

    mbed_stats_heap_get(&heap_stats);
    printf("Post-Alloc; Current heap: %lu\n", heap_stats.current_size);
    printf("Post-Alloc; Max heap size: %lu\n", heap_stats.max_size);

    free(allocation);
    printf("\nFreed 1000 bytes\n");

    mbed_stats_heap_get(&heap_stats);
    printf("Post-Free; Current heap: %lu\n", heap_stats.current_size);
    printf("Post-Free; Max heap size: %lu\n", heap_stats.max_size);
    
}

Side effects of enabling heap statistics

  • An additional 8 bytes of overhead for each memory allocation.
  • The function realloc never reuses the buffer it is resizing.
  • Memory allocation is slightly slower due to the added bookkeeping.

Stack statistics

Stack stats provide information on the allocated stack size of a thread and the worst case stack use. You can query any thread on the system for stack information.

To enable stack stats from the command-line, add the command-line flag -DMBED_STACK_STATS_ENABLED=1, for example mbed compile -DMBED_HEAP_STATS_ENABLED=1

Alternatively, to enable stack stats using mbed_app.json, add the following to mbed_app.json:

{
    "macros": [
        "MBED_STACK_STATS_ENABLED=1"
    ],
    ...
}

There are two functions you can use to access the stack stats:

  • mbed_stats_stack_get calculates combined stack informations for all threads.
  • mbed_stats_stack_get_each provides stack informations for each thread separately.

Note: These functions are available even when the stack stats are not enabled but always return zero for all fields.

Both of these functions return a struct containing the following:

Variable Description
thread_id Identifier for the thread that owns the stack or 0 if multiple threads.
max_size Maximum number of bytes used on the stack.
reserved_size Current number of bytes allocated for the stack.
stack_cnt Number of stacks stats accumulated in the structure.

Example stack statistics use cases

  • Using max_size to calibrate stack sizes for each thread.
  • Detecting which stack is close to overflowing.

Example program using stack statistics

#include "mbed.h"
#include "mbed_stats.h"

int main(void)
{
    printf("Starting stack stats example\r\n");

    int cnt = osThreadGetCount();
    mbed_stats_stack_t *stats = (mbed_stats_stack_t*) malloc(cnt * sizeof(mbed_stats_stack_t));

    cnt = mbed_stats_stack_get_each(stats, cnt);
    for (int i = 0; i < cnt; i++) {
        printf("Thread: 0x%X, Stack size: %u, Max stack: %u\r\n", 
                stats[i].thread_id, stats[i].reserved_size, stats[i].max_size);
    }
}

printf and reducing memory

The standard library family of printf (printf, sprintf, fprintf and so on) calls takes a lot of code space. This is because there are multiple format specifiers, and it is not possible to optimize the code at build time. Even a single printf call in your application pulls in the entire standard library.

A solution to reduce code space is to replace the standard printf calls with a smaller implementation.

Mbed OS provides the minimal-printf library, which offers a subset of the printf features (not all format specifiers are supported). You can also achieve further flash savings if your application does not require 64-bit integers or floating point printing.

For a memory footprint comparison between standard printf and minimal-printf, please see our Blinky size comparison.

To further reduce the memory footprint of applications that don't require file handling, enable the system I/O minimal console retarget. You can do this by enabling the configuration parameter platform.stdio-minimal-console-only. Please see the platform configuration reference for more information.

Important Information for this Arm website

This site uses cookies to store information on your computer. By continuing to use our site, you consent to our cookies. If you are not happy with the use of these cookies, please review our Cookie Policy to learn how they can be disabled. By disabling cookies, some features of the site will not work.