Mon 31 March 2025

☔️ Sane C++ March 25

SC

Welcome to the update post for March 2025!
This month has been mostly spent adding custom allocators support to all Containers!

SC::Foundation

The big Segment refactoring from last month has left so many trails also during March in SC::Foundation.
Most notably Segment now:

  1. Supports custom allocators (WIP)
  2. Uses relative pointers to store dynamically allocated block information
  3. Stores its header inline instead of making it part of the dynamic allocation

Custom allocators

Detailed list of commits:

Relative pointers

Relative pointers make it easier to create data structures that can be bulk serialized and restored skipping parsing entirely. A specific demo featuring this must still be built but this is definitively on the TODO list. Relative pointers are quite tricky to implement properly in C++ because it's very easy to trigger UB with this specific class of pointer arithmetics and this has been learned the hard way. Some tests where failing on GCC (when compiling with -fstrict-aliasing) but it has been a good occasion to learn about std::launder and std::start_lifetime_as family of functions and in general creating optimization barriers with volatile specifier.
For now relative pointers are used by default but at some point the performance impact due to their use will be assessed. In case of a significant performance difference maybe it will be re-considered possible to opt-out their usage with some runtime or compile-time flag.

Inline header

SegmentHeader is now part of Segment declaration. In other words it's no more part of its dynamically allocated part. This is useful to know all properties of a segment like its inline storage and capacity or the use of custom allocators. The inline header stores also information about needing to use the Global or the Thread-Local allocator.
This helps avoiding unnecessary thread-local access because it's useless if all allocations are known to be done from the main thread (or from a specific thread).

Virtual Memory

A class called VirtualMemory enables reserving large amounts (max value) of memory and then committing the needed one keeping existing allocation address stable. This is a big win in many cases because it avoids needing to move everything when a dynamic vector needs to expand, as long as it will be under the max value guessed during the Virtual Memory initialization. This max value can be very large as long as it stays in the allowed virtual memory available range for the given architecture, and in case of 64 bits that's pretty big.

An associated VirtualAllocator enables using it with the Segment allocator subsystem.

Memory Statistics

All allocations, deallocations and reallocations are now being counted in a Statistics class providing useful information about how many allocations are done by a given section of code. The SCTest is using such statistics to assert when detecting a mismatched number of allocate vs release calls.

Vector Growth Factor

This has been also been the occasion to feel ashamed about how many allocations where actually already done leading to make Segment grow by factor of 2 on append.

Debug visualizers

Debug visualizers have been updated to reflect changes in Segment data layout.
Also a LLDB visualizer for Array<T, N> was missing and it has been added as well.

And more

Additional minor fixes that don't deserve a whole paragraph to describe them are listed below:

SC::Build

SC::Build has received a small set of changes too. It's now always using the C++ stdlib on GCC because only more recent GCC versions (> 13) support the -nostdlib++ flag that makes it easy linking libc but not libc++ or libstdc++.

Also Makefiles for apple and linux platforms are now created in separate sub-directories. This makes it easier to manually invoke make for them as you don't need the flag to indicate a custom Makefile file name.

This is the detailed list of commits:

Minor Changes

And the obvious list of random fixes, with the notable mention of the one related to SC::Async that was randomly failing in the CI very often due to some bad copy and paste from the io_uring headers.