pw_allocator#

Flexible, safe, and measurable memory allocation

Unstable C++17 Code Size Impact: 400 to 1800 bytes

  • Flexible: Simple interface makes it easy to inject specific behaviors.

  • Safe: Can detect memory corruption, e.g overflows and use-after-free.

  • Measurable: Pick what allocations you want to track and measure.

1using pw::allocator::Allocator;
2using pw::allocator::Layout;
3
4void* AllocateNamedU32(Allocator& allocator) {
5  return allocator.Allocate(Layout::Of<NamedU32>());
6}
// Any of these allocators can be passed to the routine above.
WithBuffer<LastFitBlockAllocator<uint32_t>, 0x1000> block_allocator;
LibCAllocator libc_allocator;
TrackingAllocator tracker(libc_allocator);
SynchronizedAllocator synced(*block_allocator);

Dynamically allocate without giving up control! Pigweed’s allocators let you easily combine allocator features for your needs, without extra code size or performance penalties for those you don’t. Complex projects in particular can benefit from dynamic allocation through simplified code, improved memory usage, increased shared memory, and reduced large reservations.

Want to allocate objects from specific memory like SRAM or PSRAM?

Use dependency injection! Write your code to take Allocator parameters, and you can quickly and easily change where memory comes from or what additional features are provided simply by changing what allocator is passed:

 1class NamedU32Factory {
 2 public:
 3  using Allocator = pw::allocator::Allocator;
 4
 5  explicit NamedU32Factory(Allocator& allocator) : allocator_(allocator) {}
 6
 7  auto MakeNamedU32(std::string_view name, uint32_t value) {
 8    return allocator_.MakeUnique<NamedU32>(name, value);
 9  }
10
11 private:
12  Allocator& allocator_;
13};

Now you can easily allocate objects in the example above using SRAM, PSRAM, or both:

1// Set up an object that allocates from SRAM memory.
2PW_PLACE_IN_SECTION(".sram") std::array<std::byte, 0x1000> sram_buffer;
3pw::allocator::FirstFitBlockAllocator<uint16_t> sram_allocator(sram_buffer);
4NamedU32Factory sram_factory(sram_allocator);
5
6// Set up an object that allocates from PSRAM memory.
7PW_PLACE_IN_SECTION(".psram") std::array<std::byte, 0x2000> psram_buffer;
8pw::allocator::WorstFitBlockAllocator<uint32_t> psram_allocator(psram_buffer);
9NamedU32Factory psram_factory(psram_allocator);

Worried about forgetting to deallocate?

Use a smart pointer!

1pw::allocator::UniquePtr<NamedU32> MakeNamedU32(Allocator& allocator,
2                                                std::string_view name,
3                                                uint32_t value) {
4  return allocator.MakeUnique<NamedU32>(name, value);
5}

Want to know how much memory has been allocated?

Pick the metrics you’re interested in and track them with a TrackingAllocator:

1struct CustomMetrics {
2  PW_ALLOCATOR_METRICS_ENABLE(allocated_bytes);
3  PW_ALLOCATOR_METRICS_ENABLE(peak_allocated_bytes);
4  PW_ALLOCATOR_METRICS_ENABLE(num_failures);
5};
1  constexpr pw::metric::Token kToken = PW_TOKENIZE_STRING("CustomMetrics");
2  pw::allocator::TrackingAllocatorImpl<CustomMetrics> tracker(kToken,
3                                                              allocator);

Need to share the allocator with another thread or an interrupt handler?

Use a SynchronizedAllocator with the lock of your choice:

1void RunTasks(pw::allocator::Allocator& allocator,
2              const pw::thread::Options& options) {
3  pw::allocator::SynchronizedAllocator<pw::sync::InterruptSpinLock> synced(
4      allocator);
5  MyTask task1(synced, options);
6  MyTask task2(synced, options);
7  task1.join();
8  task2.join();
9}

Tip

Check out the Guides for even more code samples!

Is it right for you?#

Does your project need to use memory…

  • Without knowing exactly how much ahead of time?

  • From different backing storage, e.g. SRAM vs. PSRAM?

  • Across different tasks using a shared pool?

  • In multiple places, and you need to know how much each place is using?

If you answered “yes” to any of these questions, pw_allocator may be able to help! This module is designed to faciliate dynamic allocation for embedded projects that are sufficiently complex to make static allocation infeasible.

Smaller projects may be able to enumerate their objects and preallocate any storage they may need on device when running, and may be subject to extremely tight memory constraints. In these cases, pw_allocator may add more costs in terms of code size, overhead, and performance than it provides benefits in terms of code simplicity and memory sharing.

At the other extreme, larger projects may be built on platforms with rich operating system capabilities. On these platforms, the system and language runtime may already provide dynamic allocation and memory may be less constrained. In these cases, pw_allocator may not provide the same capabilities as the platform.

Between these two is a range of complex projects on RTOSes and other platforms. These projects may benefit from using the Allocator interface and its implementations to manage memory.

Guides

Integrate pw_allocator into your project and learn common use cases

API reference

Detailed description of the pw_allocator’s API

Design & roadmap

Learn why pw_allocator is designed the way it is, and upcoming plans

Code size analysis

Understand pw_allocator’s code footprint and savings potential