LCOV - code coverage report
Current view: top level - libs/capy/src - work_allocator.cpp (source / functions) Coverage Total Hit
Test: coverage_filtered.info Lines: 63.5 % 126 80
Test Date: 2025-12-30 20:31:35 Functions: 80.0 % 15 12

            Line data    Source code
       1              : //
       2              : // Copyright (c) 2025 Vinnie Falco (vinnie.falco@gmail.com)
       3              : //
       4              : // Distributed under the Boost Software License, Version 1.0. (See accompanying
       5              : // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
       6              : //
       7              : // Official repository: https://github.com/boostorg/capy
       8              : //
       9              : 
      10              : #include "work_allocator.hpp"
      11              : #include <cstdlib>
      12              : #include <functional>
      13              : #include <new>
      14              : 
      15              : namespace boost {
      16              : namespace capy {
      17              : 
      18              : //------------------------------------------------------------------------------
      19              : //
      20              : // work_allocator::arena
      21              : //
      22              : //------------------------------------------------------------------------------
      23              : 
      24            8 : work_allocator::arena::
      25              : ~arena()
      26              : {
      27            8 :     std::free(base_);
      28            8 : }
      29              : 
      30            8 : work_allocator::arena::
      31            8 : arena(std::size_t capacity)
      32            8 :     : prev_(nullptr)
      33            8 :     , next_(nullptr)
      34            8 :     , base_(std::malloc(capacity))
      35            8 :     , capacity_(capacity)
      36            8 :     , offset_(capacity)
      37            8 :     , count_(0)
      38              : {
      39            8 :     if(!base_)
      40            0 :         throw std::bad_alloc();
      41            8 : }
      42              : 
      43              : bool
      44           38 : work_allocator::arena::
      45              : owns(void* p) const noexcept
      46              : {
      47              :     std::less<char const*> cmp;
      48           38 :     auto* cp = static_cast<char const*>(p);
      49           38 :     auto* base = static_cast<char const*>(base_);
      50              :     // cp >= base && cp < base + capacity_
      51           38 :     return !cmp(cp, base) && cmp(cp, base + capacity_);
      52              : }
      53              : 
      54              : void*
      55           38 : work_allocator::arena::
      56              : allocate(std::size_t size, std::size_t align) noexcept
      57              : {
      58           38 :     if(offset_ < size)
      59            0 :         return nullptr;
      60              : 
      61           38 :     std::size_t aligned = (offset_ - size) & ~(align - 1);
      62              :     
      63           38 :     if(aligned > offset_)
      64            0 :         return nullptr;
      65              : 
      66           38 :     offset_ = aligned;
      67           38 :     ++count_;
      68           38 :     return static_cast<char*>(base_) + offset_;
      69              : }
      70              : 
      71              : void
      72           38 : work_allocator::arena::
      73              : deallocate(void* /*p*/, std::size_t /*size*/, std::size_t /*align*/) noexcept
      74              : {
      75           38 :     --count_;
      76           38 : }
      77              : 
      78              : void
      79            0 : work_allocator::arena::
      80              : reset() noexcept
      81              : {
      82            0 :     offset_ = capacity_;
      83            0 :     count_ = 0;
      84            0 : }
      85              : 
      86              : //------------------------------------------------------------------------------
      87              : //
      88              : // work_allocator
      89              : //
      90              : //------------------------------------------------------------------------------
      91              : 
      92           12 : work_allocator::
      93              : ~work_allocator()
      94              : {
      95           12 :     arena* a = head_;
      96           20 :     while(a)
      97              :     {
      98            8 :         arena* next = a->next_;
      99            8 :         delete a;
     100            8 :         a = next;
     101              :     }
     102           12 : }
     103              : 
     104           12 : work_allocator::
     105              : work_allocator(
     106              :     std::size_t min_size,
     107              :     std::size_t max_size,
     108           12 :     std::size_t keep_empty)
     109           12 :     : head_(nullptr)
     110           12 :     , tail_(nullptr)
     111           12 :     , arena_count_(0)
     112           12 :     , next_size_(min_size)
     113           12 :     , min_size_(min_size)
     114           12 :     , max_size_(max_size)
     115           12 :     , keep_empty_(keep_empty)
     116              : {
     117           12 : }
     118              : 
     119              : void*
     120           38 : work_allocator::
     121              : allocate(std::size_t size, std::size_t align)
     122              : {
     123              :     // Always allocate from tail (active arena)
     124           38 :     if(tail_)
     125              :     {
     126           30 :         if(void* p = tail_->allocate(size, align))
     127           30 :             return p;
     128              :     }
     129              : 
     130              :     // Active arena full or none exists.
     131              :     // Try recycling a parked arena to preserve allocation order.
     132            8 :     arena* fresh = find_parked();
     133            8 :     if(fresh)
     134              :     {
     135            0 :         unlink(fresh);
     136            0 :         fresh->reset();
     137            0 :         link_at_tail(fresh);
     138              :     }
     139              :     else
     140              :     {
     141            8 :         std::size_t arena_size = next_size_;
     142            8 :         if(arena_size < size + align)
     143            0 :             arena_size = size + align;
     144              : 
     145            8 :         fresh = new arena(arena_size);
     146            8 :         link_at_tail(fresh);
     147              : 
     148            8 :         if(next_size_ < max_size_)
     149              :         {
     150            8 :             next_size_ *= 2;
     151            8 :             if(next_size_ > max_size_)
     152            0 :                 next_size_ = max_size_;
     153              :         }
     154              :     }
     155              : 
     156            8 :     void* p = tail_->allocate(size, align);
     157            8 :     if(!p)
     158            0 :         throw std::bad_alloc();
     159            8 :     return p;
     160              : }
     161              : 
     162              : void
     163           38 : work_allocator::
     164              : deallocate(void* p, std::size_t size, std::size_t align) noexcept
     165              : {
     166           38 :     arena* a = find_arena(p);
     167           38 :     if(!a)
     168            0 :         return;
     169              : 
     170           38 :     a->deallocate(p, size, align);
     171              : 
     172              :     // Prune when a non-active arena empties
     173           38 :     if(a->empty() && a != tail_)
     174            0 :         prune();
     175              : }
     176              : 
     177              : void
     178            8 : work_allocator::
     179              : link_at_tail(arena* a) noexcept
     180              : {
     181            8 :     a->prev_ = tail_;
     182            8 :     a->next_ = nullptr;
     183            8 :     if(tail_)
     184            0 :         tail_->next_ = a;
     185              :     else
     186            8 :         head_ = a;
     187            8 :     tail_ = a;
     188            8 :     ++arena_count_;
     189            8 : }
     190              : 
     191              : void
     192            0 : work_allocator::
     193              : unlink(arena* a) noexcept
     194              : {
     195            0 :     if(a->prev_)
     196            0 :         a->prev_->next_ = a->next_;
     197              :     else
     198            0 :         head_ = a->next_;
     199              : 
     200            0 :     if(a->next_)
     201            0 :         a->next_->prev_ = a->prev_;
     202              :     else
     203            0 :         tail_ = a->prev_;
     204              : 
     205            0 :     a->prev_ = nullptr;
     206            0 :     a->next_ = nullptr;
     207            0 :     --arena_count_;
     208            0 : }
     209              : 
     210              : work_allocator::arena*
     211           38 : work_allocator::
     212              : find_arena(void* p) noexcept
     213              : {
     214           38 :     for(arena* a = head_; a; a = a->next_)
     215              :     {
     216           38 :         if(a->owns(p))
     217           38 :             return a;
     218              :     }
     219            0 :     return nullptr;
     220              : }
     221              : 
     222              : work_allocator::arena*
     223            8 : work_allocator::
     224              : find_parked() noexcept
     225              : {
     226              :     // Search from oldest, skip the active arena (tail)
     227            8 :     for(arena* a = head_; a && a != tail_; a = a->next_)
     228              :     {
     229            0 :         if(a->empty())
     230            0 :             return a;
     231              :     }
     232            8 :     return nullptr;
     233              : }
     234              : 
     235              : void
     236            0 : work_allocator::
     237              : prune() noexcept
     238              : {
     239              :     // Count parked (empty non-active) arenas
     240            0 :     std::size_t parked_count = 0;
     241            0 :     for(arena* a = head_; a && a != tail_; a = a->next_)
     242              :     {
     243            0 :         if(a->empty())
     244            0 :             ++parked_count;
     245              :     }
     246              : 
     247              :     // Delete excess parked arenas from the front
     248            0 :     arena* a = head_;
     249            0 :     while(a && a != tail_ && parked_count > keep_empty_)
     250              :     {
     251            0 :         arena* next = a->next_;
     252            0 :         if(a->empty())
     253              :         {
     254            0 :             unlink(a);
     255            0 :             delete a;
     256            0 :             --parked_count;
     257              :         }
     258            0 :         a = next;
     259              :     }
     260              : 
     261              :     // Shrink next_size if we're back to minimal state
     262            0 :     if(arena_count_ <= 1)
     263            0 :         next_size_ = min_size_;
     264            0 : }
     265              : 
     266              : } // capy
     267              : } // boost
     268              : 
        

Generated by: LCOV version 2.1