LCOV - code coverage report
Current view: top level - util/alloc - fd_alloc.h (source / functions) Hit Total Coverage
Test: cov.lcov Lines: 34 34 100.0 %
Date: 2026-09-08 04:28:46 Functions: 19 8496 0.2 %

          Line data    Source code
       1             : #ifndef HEADER_fd_src_util_alloc_fd_alloc_h
       2             : #define HEADER_fd_src_util_alloc_fd_alloc_h
       3             : 
       4             : /* fd_alloc is a high performance lockfree fast O(1) (typically)
       5             :    allocator.
       6             : 
       7             :    It is optimized for high concurrency use and small-ish clustered /
       8             :    multi-modal distributed allocation sizes.  It is further optimized
       9             :    for single-threaded use cases and/or when malloc-free pairs have have
      10             :    good thread affinity (i.e. frees done by the same thread that did the
      11             :    corresponding malloc).  It can also be used optimally in more complex
      12             :    threading use cases (e.g. malloc in one or more producer threads,
      13             :    free in one or more consumer threads).  It behaves well with
      14             :    irregular sizes and exploits ultra fine grained alignment for good
      15             :    packing (e.g. reasonable low memory overhead packing of byte strings
      16             :    with irregular small-ish sizes).
      17             : 
      18             :    A fd_alloc stores its state in a wksp in a persistent way and backs
      19             :    its allocations by that same wksp.  This avoids many of the severe
      20             :    performance and reliability issues of malloc
      21             : 
      22             :    Critically, it _doesn't_ _lie_ and it _doesn't_ _blow_ _up_.
      23             : 
      24             :    fd_alloc_malloc will not stall your program behind your back, calling
      25             :    the OS to grow or shrink the program's memory footprint during the
      26             :    call; it will never use more memory than has already be procured for
      27             :    the underlying wksp.  And, if fd_alloc_malloc succeeds, the returned
      28             :    memory is real and is ready for use.
      29             : 
      30             :    Obligatory dynamic allocation rant *********************************
      31             : 
      32             :    That is, fd_alloc is not the absolute unforgivable garbage of
      33             :    Linux/libc malloc.  malloc often just reserves page table entries and
      34             :    returns, irrespective of whether or not the request can be satisfied
      35             :    (on the apparent belief that the malloc call was a bluff and the user
      36             :    is probably a bad dev who doesn't bother with error trapping anyway),
      37             :    in hopes that that a later glacially slow page fault to the OS will
      38             :    actually reserve the memory.
      39             : 
      40             :    Which, even when it does work, it will by its very nature will be at
      41             :    the worst possible times (e.g. in the middle of incoming line rate
      42             :    network traffic bursts ... data structures try to grow to accommodate
      43             :    but slowing down throughput faster than they are growing at a time
      44             :    when keeping up is critical to surviving ... and then on a
      45             :    ridiculously awful normal page by normal page basis), exposing the
      46             :    caller to non-deterministic performance and reduced throughput.
      47             : 
      48             :    Unfortunately, getting overrun by DoS-like traffic patterns is the
      49             :    least of the worries.  When Linux can't back one of the page by DRAM
      50             :    on a page fault (skipping over some additional TLB and NUMA
      51             :    dubiousness that goes on under the hood), it goes from glacial
      52             :    performance to continental drift levels of performance.  It will try
      53             :    to honor the request by shuffling things to swap, exacerbating the
      54             :    above.  Suddenly it is a feat to even keep up with a 1980s modem.
      55             : 
      56             :    But that's not the end of the horror.  Because Linux thinks it cool
      57             :    to overcommit beyond physical limits for no discernible reason and
      58             :    gets flaky if you try to disable swap and/or overcommit, the page
      59             :    fault might not be able honor the commitment.  Finding itself caught
      60             :    in a lie (it can't go back in time and rescind the success that
      61             :    malloc already returned to the unsuspecting developer), the Linux
      62             :    kernel goes full HAL-9000 and starts randomly killing things.  A dead
      63             :    process can't complain about malloc lying to it after all.  And,
      64             :    cherry on top, the victims of the oom killer are frequently not even
      65             :    the culprits.
      66             : 
      67             :    Sigh ... all completely unacceptable behaviors in any situation, much
      68             :    less mission critical ones.
      69             : 
      70             :    TL;DR Friends don't let friends malloc.
      71             : 
      72             :    If you truly need malloc-free semantics, use fd_alloc.  This at least
      73             :    eliminates the most egregious horrors above.  It can't help the
      74             :    intrinsic horrors though.
      75             : 
      76             :    (Though it is ingrained in CS teaching and languages to the extent
      77             :    there's rarely even recognition of the faintest possibility of the
      78             :    existence of alternatives, people rarely truly need malloc/free
      79             :    semantics.  But, after they convince themselves they still do because
      80             :    of the brainwashing, they need to remind themselves that computers
      81             :    don't work remotely like malloc/free suggest and then should try to
      82             :    think about resource acquisition more fundamentally.  And, after they
      83             :    still manage to talk themselves back into needing it because of the
      84             :    teaching and linguistic traps, repeat ... at least if they want to
      85             :    make something fast and robust.  Even if they can prove dynamic
      86             :    allocation requests have an attainable worst level at all points in
      87             :    time, they still have to prove that heap fragmentation over time will
      88             :    never cause malloc to fail.  Good luck with that.)
      89             : 
      90             :    The above rant applies to any paired dynamic memory strategies,
      91             :    including non-placement new, implicit copy constructors, dynamic
      92             :    resizing containers, etc.  Real world computers aren't just funky
      93             :    implementations of infinite tape Turing machines.  This make-believe
      94             :    that they are in code that interacts with the real world is a recipe
      95             :    for real world disaster.
      96             : 
      97             :    End of obligatory dynamic allocation rant **************************
      98             : 
      99             :    Since it is backed by a wksp, allocations have the same NUMA, TLB,
     100             :    IPC and persistence properties of the underlying wksp.  This allows
     101             :    fd_alloc to go far beyond the capabilities of a typical allocator
     102             :    Allocations done by fd_alloc can be shared between processes (can
     103             :    even malloc in one process, translate the pointer into the address
     104             :    space of another process, and free it there, even after the first
     105             :    process has terminated), a process can be stopped and then other
     106             :    processes can still find the stopped process's allocations and use
     107             :    them / free them / etc.
     108             : 
     109             :    Regarding time efficiency and concurrency, large allocations are
     110             :    passed through to the underlying wksp allocator (which is neither
     111             :    O(1) and only "quasi"-lockfree in the sense described in fd_wksp.h).
     112             :    But the allocation strategies used under the hood (loosely inspired
     113             :    by Hoard-style lockfree allocators but with a lot of optimizations
     114             :    and tweaks for the above) are such that, in the common case of not
     115             :    needing to fall back to the underlying wksp allocator, the allocator
     116             :    is lockfree O(1).
     117             : 
     118             :    Regarding spatial efficiency, it is reasonably space efficient
     119             :    (overhead for a cstr-style allocation is ~4 bytes) and adapts over
     120             :    time to try to bound the amount of pre-allocation for small requests. */
     121             : 
     122             : #include "../wksp/fd_wksp.h"
     123             : #include "../sanitize/fd_asan.h"
     124             : 
     125             : /* FD_ALLOC_{ALIGN,FOOTPRINT} give the required alignment and footprint
     126             :    needed for a wksp allocation to be suitable as a fd_alloc.  ALIGN is
     127             :    an integer power of 2 and FOOTPRINT is an integer multiple of
     128             :    ALIGN.  These are provided to facilitate compile time declarations. */
     129             : 
     130             : #define FD_ALLOC_ALIGN     (128UL)
     131           3 : #define FD_ALLOC_FOOTPRINT sizeof(fd_alloc_t)
     132             : 
     133             : /* FD_ALLOC_MALLOC_ALIGN_DEFAULT gives the alignment that will be used
     134             :    when the user does not specify an alignment.  This will be an integer
     135             :    power of 2 of at least 16 for C/C++ allocator alignment conformance.
     136             :    (16 instead of 8 on the grounds that 128-bit is a primitive type on
     137             :    platforms with FD_HAS_INT128.) */
     138             : 
     139      700683 : #define FD_ALLOC_MALLOC_ALIGN_DEFAULT (16UL)
     140             : 
     141             : /* FD_ALLOC_JOIN_CGROUP_HINT_MAX is maximum value for a cgroup hint.
     142             :    This is an integer power of 2 minus 1 of at most FD_ALLOC_ALIGN. */
     143             : 
     144     2188185 : #define FD_ALLOC_JOIN_CGROUP_HINT_MAX (15UL)
     145             : 
     146             : /* A fd_alloc_t is a quasi-opaque handle of a fd_alloc (sizeof and
     147             :    alignof work but the internals should not be used directly). */
     148             : 
     149             : struct fd_alloc_private;
     150             : typedef struct fd_alloc_private fd_alloc_t;
     151             : 
     152             : /* fd_alloc private API ***********************************************/
     153             : 
     154             : /* FD_ALLOC_MAGIC is an ideally unique number that specifies the precise
     155             :    memory layout of a fd_alloc */
     156             : 
     157         156 : #define FD_ALLOC_MAGIC (0xF17EDA2C37A110C2UL) /* FIRE DANCER ALLOC version 2 */
     158             : 
     159             : /* FD_ALLOC_SIZECLASS_MAX is the maximum number of sizeclasses supported
     160             :    by fd_alloc. */
     161             : 
     162      835488 : #define FD_ALLOC_SIZECLASS_MAX (240UL)
     163             : 
     164             : struct __attribute__((aligned(FD_ALLOC_ALIGN))) fd_alloc_private {
     165             : 
     166             :   ulong magic;    /* ==FD_ALLOC_MAGIC */
     167             :   ulong wksp_off; /* Offset of the first byte of this structure from the start of the wksp */
     168             :   ulong tag;      /* tag that will be used by this allocator.  Positive. */
     169             : 
     170             :   uchar _[ FD_ALLOC_ALIGN - 3UL*sizeof(ulong) ]; /* Padding to FD_ALLOC_ALIGN */
     171             : 
     172             :   /* active_slot[ sizeclass + FD_ALLOC_SIZECLASS_MAX*cgroup ] is the
     173             :      global address of the superblock in circulation that is preferred
     174             :      for sizeclass allocations done by a caller in concurrency group
     175             :      cgroup.  0 if there is no active superblock currently for
     176             :      (sizeclass,cgroup).  Note that this is stored compactly but
     177             :      organized such that concurrent operations from different cgroups
     178             :      are unlikely to create false sharing. */
     179             : 
     180             :   ulong active_slot[ FD_ALLOC_SIZECLASS_MAX*(FD_ALLOC_JOIN_CGROUP_HINT_MAX+1UL) ];
     181             : 
     182             :   /* inactive_stack[ sizeclass ] gives the top of stack of inactive
     183             :      superblocks in circulation stack for sizeclass or 0 if the stack is
     184             :      empty.  This is versioned global address with a 17-bit version
     185             :      number in the least significant bits and a 50-bit gaddr encoded in
     186             :      47-bits in the most significant bits (the 3 least significant bits
     187             :      of a superblock gaddr are zero given FD_ALLOC_SUPERBLOCK_ALIGN is
     188             :      at least 8).  This means that fd_alloc can be backed by wksp up to
     189             :      ~1 PiB in size. */
     190             : 
     191             :   ulong inactive_stack[ FD_ALLOC_SIZECLASS_MAX ];
     192             : 
     193             :   /* Padding to FD_ALLOC_ALIGN here */
     194             : 
     195             : };
     196             : 
     197             : FD_PROTOTYPES_BEGIN
     198             : 
     199             : /* fd_alloc_private_join_alloc returns the local address of the alloc
     200             :    for a join. */
     201             : 
     202             : FD_FN_CONST static inline fd_alloc_t *
     203     1468656 : fd_alloc_private_join_alloc( fd_alloc_t * join ) {
     204     1468656 :   return (fd_alloc_t *)(((ulong)join) & ~FD_ALLOC_JOIN_CGROUP_HINT_MAX);
     205     1468656 : }
     206             : 
     207             : /* fd_alloc_private_wksp returns the wksp backing alloc.  Assumes alloc
     208             :    is a non-NULL pointer in the caller's address space to the fd_alloc
     209             :    (not a join handle). */
     210             : 
     211             : FD_FN_PURE static inline fd_wksp_t *
     212      823005 : fd_alloc_private_wksp( fd_alloc_t * alloc ) {
     213      823005 :   return (fd_wksp_t *)(((ulong)alloc) - alloc->wksp_off);
     214      823005 : }
     215             : 
     216             : /* fd_alloc_private_delete allows fine grained control over how much
     217             :    cleanup of the underlying wksp is done.
     218             : 
     219             :    - level<=0 indicates to do no wksp cleanup (the user can manually
     220             :      cleanup left over allocations and so forth with APIs like
     221             :      fd_wksp_tag_free).
     222             : 
     223             :    - level==1 indicates to do a quick cleanup (assuming the application
     224             :      freed all allocations done by this allocator, all wksp usage
     225             :      _except_ the shalloc itself will be freed).  fd_alloc_delete is a
     226             :      thin wrapper to this with level==1.
     227             : 
     228             :    - level>1 indicates to do a deep cleanup.  This will free all wksp
     229             :      locations that match fd_alloc's wksp tag.  IMPORTANT SAFETY TIP!
     230             :      If shalloc was allocated with the same tag, this will also free
     231             :      shalloc too!  IMPORTANT SAFETY TIP!  If any other wksp allocations
     232             :      used this tag, this will also free all those allocations too! */
     233             : 
     234             : void *
     235             : fd_alloc_private_delete( void * shalloc,
     236             :                          int    level );
     237             : 
     238             : FD_PROTOTYPES_END
     239             : 
     240             : /* End of private API *************************************************/
     241             : 
     242             : FD_PROTOTYPES_BEGIN
     243             : 
     244             : /* fd_alloc_{align,footprint} return FD_ALLOC_{ALIGN,FOOTPRINT}. */
     245             : 
     246             : FD_FN_CONST ulong
     247             : fd_alloc_align( void );
     248             : 
     249             : FD_FN_CONST ulong
     250             : fd_alloc_footprint( void );
     251             : 
     252             : /* fd_alloc_new formats an unused wksp allocation with the appropriate
     253             :    alignment and footprint as a fd_alloc.  Caller is not joined on
     254             :    return.  Returns shmem on success and NULL on failure (shmem NULL,
     255             :    shmem misaligned, shmem is not backed by a wksp ... logs details).  A
     256             :    workspace can have multiple fd_alloc created for it.  They will
     257             :    dynamically share the underlying workspace along with any other
     258             :    non-fd_alloc usage but will otherwise act as completely separate
     259             :    non-conflicting arenas (useful for logical grouping and improved
     260             :    concurrency).  To help with various diagnostics, garbage collection
     261             :    and what not, all allocations to the underlying wksp are tagged with
     262             :    the given tag, positive.  Ideally, the tag used here should be
     263             :    distinct from all other tags used by this workspace. */
     264             : 
     265             : void *
     266             : fd_alloc_new( void * shmem,
     267             :               ulong  tag );
     268             : 
     269             : /* fd_alloc_join joins the caller to a fd_alloc.  shalloc points to the
     270             :    first byte of the memory region backing the alloc in the caller's
     271             :    address space.  Returns an opaque handle of the join on success
     272             :    (IMPORTANT! THIS IS NOT JUST A CAST OF SHALLOC) and NULL on failure
     273             :    (NULL shalloc, misaligned shalloc, bad magic, ... logs details).
     274             :    Every successful join should have a matching leave.  The lifetime of
     275             :    the join is until the matching leave or the thread group is
     276             :    terminated (joins are local to a thread group).
     277             : 
     278             :    cgroup_hint is a concurrency hint used to optimize parallel and
     279             :    persistent use cases. Ideally each thread (regardless of thread
     280             :    group) should join the allocator with a different cgroup_hint system
     281             :    wide (note that joins are practically free).  And if using a fd_alloc
     282             :    in a persistent way, logical streams of execution would ideally
     283             :    preserve the cgroup_hint address starts and stops of that stream for
     284             :    the most optimal affinity behaviors.  0 is fine in single threaded
     285             :    use cases and 0 and/or collisions are fine in more general cases
     286             :    though concurrent performance might be reduced due to additional
     287             :    contention between threads that share the same cgroup_hint.  If
     288             :    cgroup_hint is not in [0,FD_ALLOC_JOIN_CGROUP_HINT_MAX], it will be
     289             :    wrapped to be in that range.
     290             : 
     291             :    TL;DR A cgroup_hint of 0 is often a practical choice single threaded.
     292             :    A cgroup_hint of fd_tile_idx() or just uniform random 64-bit value
     293             :    choice in more general situations. */
     294             : 
     295             : fd_alloc_t *
     296             : fd_alloc_join( void * shalloc,
     297             :                ulong  cgroup_hint );
     298             : 
     299             : /* fd_alloc_leave leaves an existing join.  Returns the underlying
     300             :    shalloc (IMPORTANT! THIS IS NOT A SIMPLE CAST OF JOIN) on success and
     301             :    NULL on failure.  Reasons for failure include join is NULL (logs
     302             :    details). */
     303             : 
     304             : void *
     305             : fd_alloc_leave( fd_alloc_t * join );
     306             : 
     307             : /* fd_alloc_delete unformats a wksp allocation used as a fd_alloc.
     308             :    Assumes nobody is or will be joined to the fd_alloc.  The caller
     309             :    further promises there are no allocations outstanding.  If there are
     310             :    still some outstanding allocations, it will try to clean up as many
     311             :    as it can find but it is not guaranteed to find all of them (those
     312             :    will continue to consume wksp space but could be theoretically be
     313             :    cleaned up in an application specific way by operating directly on
     314             :    the underlying workspace ... of course, if the application could do
     315             :    that, it probably such just clean up after itself before calling
     316             :    delete).  Returns shmem on success and NULL on failure (logs
     317             :    details).  Reasons for failure include shalloc is NULL, misaligned
     318             :    fd_alloc, bad magic, etc. */
     319             : 
     320             : void *
     321             : fd_alloc_delete( void * shalloc );
     322             : 
     323             : /* fd_alloc_join_cgroup_hint returns the cgroup_hint of the current
     324             :    join.  Assumes join is a current local join.  The return will be in
     325             :    [0,FD_ALLOC_JOIN_CGROUP_HINT_MAX].
     326             : 
     327             :    fd_alloc_join_cgroup_hint_set returns join with the cgroup_hint
     328             :    updated to provided cgroup_hint.  If cgroup hint is not in
     329             :    [0,FD_ALLOC_JOIN_CGROUP_HINT_MAX], it will be wrapped into this
     330             :    range.  Assumes join is a current local join.  The return value is
     331             :    not a new join. */
     332             : 
     333             : FD_FN_CONST static inline ulong
     334      718185 : fd_alloc_join_cgroup_hint( fd_alloc_t * join ) {
     335      718185 :   return ((ulong)join) & FD_ALLOC_JOIN_CGROUP_HINT_MAX;
     336      718185 : }
     337             : 
     338             : FD_FN_CONST static inline fd_alloc_t *
     339             : fd_alloc_join_cgroup_hint_set( fd_alloc_t * join,
     340         672 :                                ulong        cgroup_hint ) {
     341         672 :   return (fd_alloc_t *)((((ulong)join) & (~FD_ALLOC_JOIN_CGROUP_HINT_MAX)) | (cgroup_hint & FD_ALLOC_JOIN_CGROUP_HINT_MAX));
     342         672 : }
     343             : 
     344             : /* fd_alloc_wksp returns a pointer to a local wksp join of the wksp
     345             :    backing the fd_alloc with the current local join.  Caller should not
     346             :    call fd_alloc_leave on the returned value.  Lifetime of the returned
     347             :    wksp handle is as long as the shalloc used on the fd_alloc_join is
     348             :    still mapped into the caller's address space.
     349             : 
     350             :    fd_alloc_tag returns the tag that will be used for allocations from
     351             :    this workspace. */
     352             : 
     353             : FD_FN_PURE static inline fd_wksp_t * // NULL indicates NULL join
     354          42 : fd_alloc_wksp( fd_alloc_t * join ) {
     355          42 :   fd_alloc_t * alloc = fd_alloc_private_join_alloc( join );
     356          42 :   return FD_LIKELY( alloc ) ? fd_alloc_private_wksp( alloc ) : NULL;
     357          42 : }
     358             : 
     359             : FD_FN_PURE static inline ulong // Positive, 0 indicates NULL join
     360          15 : fd_alloc_tag( fd_alloc_t * join ) {
     361          15 :   fd_alloc_t * alloc = fd_alloc_private_join_alloc( join );
     362          15 :   return FD_LIKELY( alloc ) ? alloc->tag : 0UL;
     363          15 : }
     364             : 
     365             : /* fd_alloc_malloc_at_least allocates at least sz bytes with alignment
     366             :    of at least align from the wksp backing the fd_alloc.  join is a
     367             :    current local join to the fd_alloc.  align should be an integer power
     368             :    of 2 or 0.
     369             : 
     370             :    An align of 0 indicates to use FD_ALLOC_MALLOC_DEFAULT_ALIGN for the
     371             :    request alignment.  This will be large enough such that
     372             :    fd_alloc_malloc is conformant with C/C++ alignment specifications
     373             :    (i.e. can trivially wrap fd_alloc_malloc to use as a drop in
     374             :    replacement for malloc).
     375             : 
     376             :    Small values of align will NOT be rounded up to some minimum (e.g.
     377             :    allocating lots of 1 byte aligned short strings is fine and
     378             :    relatively space and time efficient ... the overhead is ~4 bytes per
     379             :    allocation).  fd_alloc is not particularly optimized when align>~sz
     380             :    and/or large alignments (>~4096B).  While large values for align are
     381             :    supported by fd_alloc_malloc, directly using fd_wksp_alloc is
     382             :    recommended in such cases.
     383             : 
     384             :    If an allocation is "large" (align + sz >~ 64KiB for the current
     385             :    implementation), it will be handled by fd_wksp_alloc under the hood.
     386             :    Otherwise, it will be handled by fd_alloc_malloc algorithms (which
     387             :    are ultimately backed by fd_wksp_alloc).  As such, if a small
     388             :    allocation is "new" (e.g. first allocation of a size around sz, an
     389             :    allocation that can't be packed near other existing allocations
     390             :    around that sz, etc), this might also fallback on fd_wksp_alloc.
     391             :    Typically though, after initial allocation and/or program warmup,
     392             :    fd_alloc_malloc calls will be a reasonably fast O(1) lockfree.
     393             : 
     394             :    Returns a pointer to the allocation in the local address space on
     395             :    success.  Note that this pointer will a wksp laddr.  As such, it can
     396             :    be converted to a gaddr, passed to other threads in other thread
     397             :    groups, and converted to a wksp laddr in their address spaces, freed
     398             :    via a join to the fd_alloc in that thread group, persisted beyond the
     399             :    lifetime of the calling thread, etc.
     400             : 
     401             :    Returns NULL on failure (silent to support HPC usage) or when sz is
     402             :    0.  Reasons for failure include NULL join, invalid align, sz overflow
     403             :    (sz+align>~2^64), no memory available for request (e.g. workspace has
     404             :    insufficient room or is too fragmented).
     405             : 
     406             :    On return, *max will contain the number actual number of bytes
     407             :    available at the returned gaddr.  On success, this will be at least
     408             :    sz and it is not guaranteed to be a multiple of align.  On failure,
     409             :    *max will be zero.
     410             : 
     411             :    fd_alloc_malloc is a simple wrapper around fd_alloc_malloc_at_least
     412             :    for use when applications do not care about the actual size of their
     413             :    allocation. */
     414             : 
     415             : void *
     416             : fd_alloc_malloc_at_least( fd_alloc_t * join,
     417             :                           ulong        align,
     418             :                           ulong        sz,
     419             :                           ulong *      max );
     420             : 
     421             : static inline void *
     422             : fd_alloc_malloc( fd_alloc_t * join,
     423             :                  ulong        align,
     424      263058 :                  ulong        sz ) {
     425      263058 :   ulong max[1];
     426      263058 :   void * laddr = fd_alloc_malloc_at_least( join, align, sz, max );
     427             : 
     428             : #if FD_HAS_DEEPASAN
     429             :   if( FD_LIKELY( laddr ) ) fd_asan_poison( (uchar *)laddr + sz, (*max) - sz );
     430             : #endif
     431             : 
     432      263058 :   return laddr;
     433      263058 : }
     434             : 
     435             : /* FIXME: consider a fd_alloc_avail API that returns the max bytes avail
     436             :    at an allocation? */
     437             : 
     438             : /* fd_alloc_free frees the outstanding allocation whose first byte is
     439             :    pointed to by laddr in the caller's local address space.  join is a
     440             :    current local join to the fd_alloc.  The caller promises laddr was
     441             :    allocated by the underlying fd_alloc (but not necessarily on the
     442             :    calling thread or even in this calling process or even by a thread /
     443             :    process that is still running).  Silent for HPC usage (NULL join and
     444             :    NULL laddr are a no-op).
     445             : 
     446             :    Like fd_alloc_malloc, if the allocation was large, this will be
     447             :    handled by fd_wksp_free under the hood, which is neither lockfree nor
     448             :    O(1).  If the allocation was small, this will typically be lockfree
     449             :    O(1).  It is possible that, if the amount of outstanding small
     450             :    allocations has reduced significantly, fd_alloc_free on a small
     451             :    allocation might trigger a fd_wksp_free to free up wksp space for
     452             :    other usage (including uses not through this fd_alloc).
     453             : 
     454             :    (It would be possible to implement this less efficiently in space and
     455             :    time such that join didn't need to be passed.  The current design has
     456             :    picked efficiency and consistency with other APIs though.)
     457             : 
     458             :    Note that this will implicitly optimize the freed memory to be
     459             :    preferentially reused by the join's concurrency group.  Thus the
     460             :    caller should have at least one join for each concurrency group to
     461             :    which it might want to return memory for reuse and then call free
     462             :    with the appropriate join. */
     463             : 
     464             : void
     465             : fd_alloc_free( fd_alloc_t * join,
     466             :                void *       laddr );
     467             : 
     468             : /* fd_alloc_compact frees all wksp allocations that are not required
     469             :    for any outstanding user mallocs (note that fd_alloc_free lazily
     470             :    returns unused memory from the underlying wksp to accelerate
     471             :    potential future allocations).  join is a current local join to the
     472             :    alloc.  This cannot fail from a user's POV but logs any wonkiness
     473             :    detected.
     474             : 
     475             :    fd_alloc_compact has the property that it minimizes the amount of
     476             :    wksp utilization for the set of outstanding user mallocs when there
     477             :    is no other concurrent alloc usage.  As such, if there is no
     478             :    concurrent alloc usage _and_ there are no outstanding mallocs, on
     479             :    return, all wksp allocations (except the user provided memory region
     480             :    that holds the state of the allocator) will be returned to the wksp.
     481             :    This can be then be used to reset the alloc and/or implement robust
     482             :    leak detection at program teardown.
     483             : 
     484             :    This function is safe to use even when there is other concurrent
     485             :    alloc usage.  It t is best effort in that case; it is not guaranteed
     486             :    that there was some point in time between call and return when the
     487             :    wksp utilization was minimized for the contemporaneous set of
     488             :    outstanding user mallocs.
     489             : 
     490             :    Also note that this function is not O(1) and the fd_alloc_free lazy
     491             :    return mechanism does not permit unbounded growth of unreturned free
     492             :    memory.  So this should be used sparingly at best (e.g. in teardown
     493             :    leak detection or rare non-critical path housekeeping). */
     494             : 
     495             : void
     496             : fd_alloc_compact( fd_alloc_t * join );
     497             : 
     498             : /* fd_alloc_is_empty returns 1 if the alloc has no outstanding mallocs
     499             :    and 0 otherwise.  join is a current local join to the alloc.  NULL
     500             :    join silently returns 0.
     501             : 
     502             :    Important safety tip!  This should only be run when there is no
     503             :    concurrent alloc usage.  It is not algorithmically fast.  This might
     504             :    temporarily lock the underlying wksp while running and might call
     505             :    fd_alloc_compact under the hood.  It assumes the user provided memory
     506             :    region holding the alloc state is contained within a region returned
     507             :    by a single fd_wksp_alloc call (it would be hard to create an alloc
     508             :    where that isn't the case).  It assumes alloc is the only user of the
     509             :    alloc's tag in the wksp.  As such this should be used carefully and
     510             :    sparingly (e.g. at program teardown for leak detection).
     511             : 
     512             :    It will "work" with concurrent alloc usage in that the return value
     513             :    will be in 0 or 1 and it will not corrupt the alloc or underlying
     514             :    wksp.  But the return value will not be well-defined (e.g. it is not
     515             :    guaranteed to correspond the state of the alloc at some point in time
     516             :    between when this was called and it when it returned). */
     517             : 
     518             : int
     519             : fd_alloc_is_empty( fd_alloc_t * join );
     520             : 
     521             : /* fd_alloc_max_expand computes a recommended value to use for max when
     522             :    needing to dynamically resize structures.  The below is very subtle
     523             :    and fixes a lot of pervasive errors with dynamic resizing
     524             :    implementations (either explicit or implicitly done under the hood).
     525             :    It doesn't fix the main error with dynamic resizing though.  The main
     526             :    error being deciding to use anything with dynamic resizing (outside
     527             :    of, maybe, initialization at program start).
     528             : 
     529             :    Consider an all too common case of an initially too small dynamically
     530             :    sized array that is getting elements appended to it one at a time.
     531             :    E.g. without proper error trapping, overflow handling and the like:
     532             : 
     533             :      foo_t * foo       = NULL;
     534             :      ulong   foo_max   = 0UL;
     535             :      ulong   foo_cnt   = 0UL;
     536             :      ulong   foo_delta = ... some reasonable increment ...;
     537             : 
     538             :      while( ... still appending ... ) {
     539             : 
     540             :        if( foo_cnt==foo_max ) { // Need to resize
     541             :          foo_max += foo_delta;
     542             :          foo = (foo_t *)realloc( foo, foo_max*sizeof(foo_t) );
     543             :        }
     544             : 
     545             :        foo[ foo_cnt++ ] = ... next val to append ...;
     546             :      }
     547             : 
     548             :    This is terrible theoretically and practically and yet it looks like
     549             :    it does everything right.
     550             : 
     551             :    The theoretical issue is that, if the realloc can't be done in-place
     552             :    (which is more common than most realize ... depends on how the
     553             :    underlying realloc implementation details), the memory will have to
     554             :    be copied from the original location to the resized location with a
     555             :    typical cost of final_foo_max/2 -> O(final_foo_cnt).  Because max is
     556             :    increased by fixed absolute amount each resizing, there will be
     557             :    final_foo_cnt/foo_delta -> O(final_foo_cnt) such resizes.
     558             : 
     559             :    That is, we've accidentally written a method that has a slow
     560             :    O(final_foo_cnt^2) worst case even though it superficially looks like
     561             :    a fast O(final_foo_cnt) method.  Worse still, this behavior might
     562             :    appear suddenly in previously fine code if realloc implementation
     563             :    changes or, yet again worse, because a larger problem size was used
     564             :    in the wild than used in testing.
     565             : 
     566             :    The practical issue is realloc is painfully slow and it gets worse
     567             :    for large sizes because large sizes are usually handled by operating
     568             :    system calls (e.g. mmap or sbrk under the hood).  We've also now done
     569             :    O(final_foo_cnt) slow operating system calls in our already
     570             :    algorithmically slow O(final_foo_cnt^2) worst case algorithm that
     571             :    still superficially looks like a fast O(final_foo_cnt).  (And throw
     572             :    in the other issues with malloc described above about TLB and NUMA
     573             :    inefficiency, the gaslighting the kernel does "clearly the crash had
     574             :    nothing to do with the OOM killer shooting processes randomly in the
     575             :    head, your program probably just had a bug ... yeah ... that's the
     576             :    ticket" ... for good measure).
     577             : 
     578             :    We can get an algorithmic improvement if we change the above to
     579             :    increase max by a fixed relative amount each resize.  Since we are
     580             :    dealing with integers though, we should make sure that we always
     581             :    increase max by some minimal amount.  Instead of:
     582             : 
     583             :      foo_max += foo_delta;
     584             : 
     585             :    we can use something like:
     586             : 
     587             :      foo_max = fd_ulong_max( foo_max*gamma, foo_max + foo_delta );
     588             : 
     589             :    If gamma>1, asymptotically, we will only do O(lg cnt) resizes.
     590             :    Theoretically, we've gone from an O(final_foo_cnt^2) worst case
     591             :    method to an O(final_foo_cnt lg final_foo_cnt) worst case method.  It
     592             :    is still irritating that it looks superficially like a fast
     593             :    O(final_foo_cnt) method but this is amongst the many reasons why
     594             :    dynamic resizing is gross and wrong and to be avoided when possible.
     595             : 
     596             :    The larger gamma is, the smaller the leading coefficient is in the
     597             :    O(final_foo_cnt lg final_foo_cnt) and thus the better this
     598             :    approximates the fast O(final_foo_cnt) method that it superficially
     599             :    seems to be.  But using a very large gamma is clearly absurd.  There
     600             :    are obvious memory footprint limitations for large sizes and each
     601             :    resize would trigger an ever larger amount of OS work.  This raises
     602             :    the question:
     603             : 
     604             :    What is the optimal gamma?
     605             : 
     606             :    Suppose we have worst case realloc implementation (alloc new memory,
     607             :    copy, free old memory and, when no free fragment large enough is
     608             :    available, use sbrk like semantics to get memory from the O/S ...
     609             :    not uncommon as it is trivial to implement and often works "good
     610             :    enough" in lab settings).  It always works out-of-place and it always
     611             :    just appends new memory at the end of the heap when the heap runs out
     612             :    of space.  Then, while doing the above, asymptotically, we expect the
     613             :    heap to look something like:
     614             : 
     615             :      other allocs | M foo_t alloc | padding free | unmapped
     616             : 
     617             :    On the next resize, we'd request space for M gamma foo_t.  Since
     618             :    there are no free fragments large enough for this, realloc is going
     619             :    to have to map some space from the operating system, copy our memory
     620             :    into it and free up the original space for reuse.  Post resize, we
     621             :    expect the heap to look like:
     622             : 
     623             :      other allocs | M foo_t free | M gamma foo_t alloc | padding free | unmapped
     624             : 
     625             :    On the next resize, we'd request space for M gamma^2 foo_t.  This
     626             :    also can't fit within any free fragment above for gamma>1 (noting
     627             :    that, in this worst case realloc, we have to allocate the memory
     628             :    first and then copy and then free the old).  So we end up with:
     629             : 
     630             :      other allocs | M (1+gamma) foo_t free | M gamma^2 foo_t alloc | padding free | unmapped
     631             : 
     632             :    On the next resize, we'd request space for M gamma^3 foo_t.  If we
     633             :    have:
     634             : 
     635             :      gamma^3 < 1 + gamma
     636             : 
     637             :    we can fit this request in the hole left by the two previous resizes.
     638             :    This implies we need gamma<1.32471... where the magic number is the
     639             :    positive real root of:
     640             : 
     641             :      x^3 - x - 1  = 0
     642             : 
     643             :    This is the "silver ratio" in the sense that the positive real root
     644             :    of x^2 - x - 1 is the "golden ratio" of 1.61803...  (Note that the
     645             :    golden ratio would apply if we had a more sophisticated realloc under
     646             :    the hood that aliased the resized allocation over top the M foo_t
     647             :    free and the existing M gamma foo_t alloc and then moved the aliased
     648             :    memory.  Presumably such a sophisticated realloc would also just
     649             :    append to the end of the heap without any move or copy at all but
     650             :    that eventually leads to a question about how much overallocation and
     651             :    operating system overhead is acceptable on resize discussed further
     652             :    below).
     653             : 
     654             :    After a resize with something near but smaller than the silver ratio,
     655             :    we expect the heap to look like:
     656             : 
     657             :      other allocs | M gamma^3 foo_t alloc | padding free | unmapped
     658             : 
     659             :    which is back to where we started, except with a larger allocation.
     660             : 
     661             :    We don't want to be doing floating point math in methods like this.
     662             :    Noting that gamma = 1 + 1/4 + 1/16 = 1.3125 is very close to the
     663             :    silver yields the very practical:
     664             : 
     665             :      new_max = fd_ulong_max( max + (max>>2) + (max>>4), max + delta );
     666             : 
     667             :    This is friendly with even the worst case realloc behaviors under the
     668             :    hood.  It also works will in similar situations with linear storage
     669             :    media (e.g. disk storage).  The limit also means that the worst case
     670             :    overallocation for cases like the above at most ~32% and on average
     671             :    ~16%.  This is a comparable level of overallocation that already
     672             :    happens under the hood (e.g. on par with the level of waste that
     673             :    naturally happens in allocators for metadata and padding and much
     674             :    less waste than the golden ratio or larger growth rates if we
     675             :    dubiously trust that the realloc method under the hood).
     676             : 
     677             :    In cases where we might need to resize to even larger than this, we
     678             :    just resize to the caller's requested amount and keep our fingers
     679             :    crossed that the caller realized by this time dynamic resizing was a
     680             :    mistake and is allocating the correct size this time.
     681             : 
     682             :    Adding arithmetic overflow handling then yields the below.
     683             : 
     684             :    TL;DR  Example usage (ignoring size calculation overflow handling and
     685             :    allocation error trapping):
     686             : 
     687             :      ulong   foo_cnt   = 0UL;
     688             :      ulong   foo_max   = ... good estimate the actual amount needed;
     689             :      ulong   foo_delta = ... reasonable minimum resizing increment;
     690             :      foo_t * foo       = (foo_t *)malloc( foo_max*sizeof(foo_t) );
     691             : 
     692             :      while( ... still appending ... ) {
     693             : 
     694             :        if( FD_UNLIKELY( foo_cnt==foo_max ) ) {
     695             :          foo_max = fd_alloc_max_expand( foo_max, foo_delta, foo_cnt + foo_delta );
     696             :          foo     = (foo_t *)realloc( foo, foo_max*sizeof(foo_t) );
     697             :        }
     698             : 
     699             :        foo[ foo_cnt++ ] = ... next val to append ...;
     700             : 
     701             :      }
     702             : 
     703             :      ... at this point
     704             :      ... - foo has foo_cnt elements initialized
     705             :      ... - foo has room for foo_max elements total
     706             :      ... - when the initial foo_max estimate was correct or oversized,
     707             :      ...   no resizing was done
     708             :      ... - when the initial foo_max was undersized, asymptotically,
     709             :      ...   foo_max is at most ~32% larger worst case (~16% larger
     710             :      ...   average case) than foo_cnt with at most O(lg foo_cnt)
     711             :      ...   reallocs needed to initialize foo.
     712             :      ... - the resizing test branch is highly predictable
     713             :      ... - the underlying heap shouldn't be too fragmented or
     714             :      ...   overallocated regardless of the allocator implementation
     715             :      ...   details. */
     716             : 
     717             : FD_FN_CONST static inline ulong       /* new_max, new_max>=max(needed,max), if max<ULONG_MAX, will be new_max>max */
     718             : fd_alloc_max_expand( ulong max,
     719             :                      ulong delta,     /* Assumed > 0 */
     720     3000000 :                      ulong needed ) {
     721     3000000 :   ulong t0 = max + delta;               t0 = fd_ulong_if( t0<max, ULONG_MAX, t0 ); /* Handle overflow */
     722             :   ulong t1 = max + (max>>2) + (max>>4); t1 = fd_ulong_if( t1<max, ULONG_MAX, t1 ); /* Handle overflow */
     723     3000000 :   return fd_ulong_max( fd_ulong_max( t0, t1 ), needed );
     724     3000000 : }
     725             : 
     726             : FD_PROTOTYPES_END
     727             : 
     728             : #endif /* HEADER_fd_src_util_alloc_fd_alloc_h */

Generated by: LCOV version 1.14