O1Heap Documentation

repository·master·Indexed 19 days ago

https://github.com/pavel-kirienko/o1heap

A highly deterministic, constant-complexity (O(1)) memory allocator designed for hard real-time, high-integrity embedded systems. It features low worst-case execution time (WCET) and predictable memory fragmentation using a modified half-fit allocation strategy. The library provides a constant-time API including o1heapInit, o1heapAllocate, and o1heapFree, and includes a HITL performance test suite for the RP2350 (Pico 2).

Tokens
2.1K
Snippets
4
Records
9
Agent score
15%

What's inside O1Heap

  1. Understand the half-fit allocation strategy

    master

    The library implements a modified half-fit algorithm. Memory is allocated in fragments whose size is rounded up to the next integer power of two.

    Key Characteristics:

    • Constant-time complexity: Both allocation and deallocation are $O(1)$.
    • Predictable fragmentation: The worst-case memory consumption is well-characterized, making it suitable for hard real-time and high-integrity systems.
    • Metadata Overhead: Each allocation incurs an overhead $a$, defined by O1HEAP_ALIGNMENT. This value also dictates the pointer alignment. On 32-bit platforms, this overhead is typically 8 bytes (2 $\times$ pointer width).
    • Cache Optimization: The implementation uses the most recently used memory fragments to minimize cache misses.

    Comparison of Strategies:

    Allocation strategyWCMC
    First-fit$H = M \ (1 + \lceil{} \log_2 n \rceil{})$
    Half-fit$H = 2 M \ (1 + \lceil{} \log_2 n \rceil{})$
    Best-fit$H = M \ (n - 2)$
    TLSF(see best-fit)
  2. How to size the heap space to prevent OOM

    master

    To ensure that out-of-memory (OOM) failures cannot occur at runtime, you must provide enough memory to account for the Worst-Case Memory Consumption (WCMC). This implementation uses a modified half-fit algorithm where memory is allocated in fragments rounded up to the next power of two.

    To calculate the total required heap space in bytes ($H_b$), use the following refined WCMC model:

    $$H_b(M,n,l,a) = a \cdot k + \frac{ 2 \cdot l \cdot n \cdot M_f \ (\lceil{} \log_2 n_f \rceil{} + 1) }{ l+n }$$

    Parameters:

    • $M$: Peak total memory requirement of the application (sum of all allocated fragments at peak utilization).
    • $n$: Maximum contiguous fragment size requested by the application.
    • $l$: The smallest amount of memory that may be requested (chosen freely by the designer).
    • $a$: The per-allocation metadata overhead, represented by O1HEAP_ALIGNMENT (platform-dependent; e.g., 8 bytes on 32-bit platforms).
    • $n_f = \lceil{} \frac{n}{l} \rceil{}$
    • $M_f = \lceil{} \frac{M}{l} \rceil{}$
    • $k = M_f - n_f + 1$

    By providing at least $H_b$ bytes, you guarantee that 'catastrophic fragmentation' (where the allocator cannot serve a request despite having enough total free memory) cannot occur.

  3. MISRA compliance and development standards

    master

    For developers contributing to or using the library in high-integrity environments:

    • MISRA Compliance: Enforced via Clang-Tidy.
    • Code Style: All code must be formatted with clang-format.
    • Handling Violations: Intentional MISRA violations must be documented and justified in-place using the following pattern:
    // Intentional violation of MISRA: <valid reason here>
    // NOLINT(*-specific-rule)
    • Releasing Versions: Update the version number macro in the header file and create a new semver git tag (e.g., 2.3.0).
  4. Run the HITL performance test suite

    master

    After building the UF2 file, follow these steps to run the performance test:

    1. Flash the device: Put the Pico 2 into BOOTSEL mode and drag/drop the build/o1heap_perftest.uf2 file onto the device.
    2. Connect hardware: Connect a UART adapter to GPIO0 (TX) and GND.
    3. Open serial terminal: Use a terminal emulator configured to 115200 8N1.

    Alternatively, if you have the Pico SDK installed, you can automate the build, flash, and serial console setup using the provided script:

    # Manual serial connection
    picocom -b 115200 /dev/ttyUSB0
    
    # Automated build, flash, and run
    ./run.sh --sdk ~/apps/pico-sdk
  5. Build the HITL performance test suite

    master

    The HITL performance test suite is firmware for the RP2350 (Pico 2) that measures CPU cycles for O1Heap allocation and free operations. To build it, you need the pico-sdk installed. Use cmake to configure the build with the pico2 board type and then build the project. The resulting UF2 file will be located at build/o1heap_perftest.uf2.

    export PICO_SDK_PATH=/path/to/pico-sdk
    cmake -S . -B build -DPICO_BOARD=pico2
    cmake --build build
  6. Integrate O1Heap into your project

    master

    O1Heap is designed for easy integration into embedded projects. Follow these steps:

    1. Copy Source Files: Copy o1heap.c and o1heap.h from the o1heap/ directory into your project tree.
    2. Configure Includes: Ensure the directory containing o1heap.h is in your compiler's include search paths.
    3. Prepare Memory Arena: Dedicate a memory arena (a block of memory) for the heap. Ensure the arena is properly aligned according to O1HEAP_ALIGNMENT.
    4. Initialize: Call o1heapInit(..) with a pointer to your arena and its size.
    5. Manage Concurrency: O1Heap does not handle concurrent access. If your application uses multiple threads or interrupts that access the heap, you must implement your own locking mechanism.

    Note: No special compiler options are required for standard compilation.

    /* Basic integration pattern */
    #include "o1heap.h"
    #include <stdalign.h>
    
    // Define an aligned arena
    static alignas(O1HEAP_ALIGNMENT) unsigned char heap_arena[32768];
    
    void setup() {
        O1HeapInstance* heap = o1heapInit(heap_arena, sizeof(heap_arena));
        if (heap == NULL) {
            // Handle error: arena not aligned or too small
        }
    }
  7. Configure the library via build-time options

    master

    The library behavior can be modified using several build-time options:

    • O1HEAP_CONFIG_HEADER: Enables support for a custom configuration header.
    • O1HEAP_TRACE: Enables optional trace events for debugging (available in v2.2+).
    • O1HEAP_CLZ(x): Used internally to accelerate (de-)allocation using fast CLZ intrinsics.

    Note: In version 3.0, the per-fragment overhead was reduced from 4$\times$ pointer width to 2$\times$ pointer width to save memory, which may result in a slight performance trade-off.

  8. Configure O1Heap via preprocessor macros

    master

    You can fine-tune the O1Heap implementation using several preprocessor macros. These can be defined in your code or via compiler flags.

    MacroDescription
    O1HEAP_CONFIG_HEADERDefine as "path/to/config.h" to pass configuration macros via a header file instead of command-line flags.
    O1HEAP_ASSERT(x)Customize assertion handling. To disable assertions, define it as (void)(x). Defaults to assert(x) from <assert.h>.
    O1HEAP_LIKELY(x) & O1HEAP_UNLIKELY(x)Branch weighting hints for compiler optimization. Should expand to compiler-specific intrinsics or simply (x).
    O1HEAP_CLZ(x)Count Leading Zeros function. For performance, override this with a compiler intrinsic (e.g., __builtin_clzl(x) for GCC/Clang) if the default is not used.

    Warning: If O1HEAP_CLZ(x) is not mapped to a hardware-supported intrinsic on your architecture, performance will degrade significantly.

  9. Use the O1Heap API for allocation and deallocation

    master

    O1Heap provides a constant-time memory management API with semantics similar to malloc and free.

    • o1heapInit(arena_ptr, size): Initializes the heap using the provided memory arena. Returns an O1HeapInstance* or NULL if initialization fails (e.g., due to alignment or insufficient size).
    • o1heapAllocate(heap, size): Allocates size bytes of memory. This operation has a constant worst-case execution time (WCET).
    • o1heapFree(heap, ptr): Deallocates the memory pointed to by ptr. This operation also has a constant WCET.
    • o1heapDoInvariantsHold(heap): (Optional) Periodically invoke this to verify that the heap's internal data structures remain intact and uncorrupted.
    #include "o1heap.h"
    
    // ... initialization code ...
    
    void* ptr = o1heapAllocate(heap, 200); // Constant-time allocation
    if (ptr != NULL) {
        // Use memory
        o1heapFree(heap, ptr); // Constant-time deallocation
    }
    
    // Periodically check integrity
    o1heapDoInvariantsHold(heap);