LCOV - code coverage report
Current view: top level - capy/ex - recycling_memory_resource.hpp (source / functions) Coverage Total Hit
Test: coverage_filtered.info Lines: 85.5 % 62 53
Test Date: 2026-02-04 19:49:07 Functions: 91.7 % 12 11

            Line data    Source code
       1              : //
       2              : // Copyright (c) 2025 Vinnie Falco (vinnie dot falco at gmail dot 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/cppalliance/capy
       8              : //
       9              : 
      10              : #ifndef BOOST_CAPY_RECYCLING_MEMORY_RESOURCE_HPP
      11              : #define BOOST_CAPY_RECYCLING_MEMORY_RESOURCE_HPP
      12              : 
      13              : #include <boost/capy/detail/config.hpp>
      14              : 
      15              : #include <bit>
      16              : #include <cstddef>
      17              : #include <memory_resource>
      18              : #include <mutex>
      19              : 
      20              : namespace boost {
      21              : namespace capy {
      22              : 
      23              : /** Recycling memory resource with size-class buckets.
      24              : 
      25              :     This memory resource recycles memory blocks using power-of-two
      26              :     size classes for O(1) allocation lookup. It maintains a thread-local
      27              :     pool for fast lock-free access and a global pool for cross-thread
      28              :     block sharing.
      29              : 
      30              :     Size classes: 64, 128, 256, 512, 1024, 2048 bytes.
      31              :     Allocations larger than 2048 bytes bypass the pools entirely.
      32              : 
      33              :     This is the default allocator used by run_async when no allocator
      34              :     is specified.
      35              : 
      36              :     @par Thread Safety
      37              :     Thread-safe. The thread-local pool requires no synchronization.
      38              :     The global pool uses a mutex for cross-thread access.
      39              : 
      40              :     @par Example
      41              :     @code
      42              :     auto* mr = get_recycling_memory_resource();
      43              :     run_async(ex, mr)(my_task());
      44              :     @endcode
      45              : 
      46              :     @see get_recycling_memory_resource
      47              :     @see run_async
      48              : */
      49              : class recycling_memory_resource : public std::pmr::memory_resource
      50              : {
      51              :     static constexpr std::size_t num_classes = 6;
      52              :     static constexpr std::size_t min_class_size = 64;   // 2^6
      53              :     static constexpr std::size_t max_class_size = 2048; // 2^11
      54              :     static constexpr std::size_t bucket_capacity = 16;
      55              : 
      56              :     static std::size_t
      57         9506 :     round_up_pow2(std::size_t n) noexcept
      58              :     {
      59         9506 :         return n <= min_class_size ? min_class_size : std::bit_ceil(n);
      60              :     }
      61              : 
      62              :     static std::size_t
      63         9506 :     get_class_index(std::size_t rounded) noexcept
      64              :     {
      65         9506 :         std::size_t idx = std::countr_zero(rounded) - 6;  // 64 = 2^6
      66         9506 :         return idx < num_classes ? idx : num_classes;
      67              :     }
      68              : 
      69              :     struct pool
      70              :     {
      71              :         struct bucket
      72              :         {
      73              :             std::size_t count = 0;
      74              :             void* ptrs[bucket_capacity];
      75              : 
      76         4790 :             void* pop() noexcept
      77              :             {
      78         4790 :                 if(count == 0)
      79          117 :                     return nullptr;
      80         4673 :                 return ptrs[--count];
      81              :             }
      82              : 
      83              :             // Peter Dimov's idea
      84          117 :             void* pop(bucket& b) noexcept
      85              :             {
      86          117 :                 if(count == 0)
      87          117 :                     return nullptr;
      88            0 :                 for(std::size_t i = 0; i < count; ++i)
      89            0 :                     b.ptrs[i] = ptrs[i];
      90            0 :                 b.count = count - 1;
      91            0 :                 count = 0;
      92            0 :                 return b.ptrs[b.count];
      93              :             }
      94              : 
      95         4677 :             bool push(void* p) noexcept
      96              :             {
      97         4677 :                 if(count >= bucket_capacity)
      98            4 :                     return false;
      99         4673 :                 ptrs[count++] = p;
     100         4673 :                 return true;
     101              :             }
     102              :         };
     103              : 
     104              :         bucket buckets[num_classes];
     105              : 
     106           49 :         ~pool()
     107              :         {
     108          343 :             for(auto& b : buckets)
     109          411 :                 while(b.count > 0)
     110          117 :                     ::operator delete(b.pop());
     111           49 :         }
     112              :     };
     113              : 
     114              :     static pool&
     115         9463 :     local() noexcept
     116              :     {
     117         9463 :         static thread_local pool p;
     118         9463 :         return p;
     119              :     }
     120              : 
     121              :     static pool&
     122          121 :     global() noexcept
     123              :     {
     124          121 :         static pool p;
     125          121 :         return p;
     126              :     }
     127              : 
     128              :     static std::mutex&
     129          121 :     global_mutex() noexcept
     130              :     {
     131              :         static std::mutex mtx;
     132          121 :         return mtx;
     133              :     }
     134              : 
     135              : protected:
     136              :     void*
     137         4753 :     do_allocate(std::size_t bytes, std::size_t) override
     138              :     {
     139         4753 :         std::size_t rounded = round_up_pow2(bytes);
     140         4753 :         std::size_t idx = get_class_index(rounded);
     141              : 
     142         4753 :         if(idx >= num_classes)
     143           80 :             return ::operator new(bytes);
     144              : 
     145         4673 :         if(auto* p = local().buckets[idx].pop())
     146         4556 :             return p;
     147              : 
     148              :         {
     149          117 :             std::lock_guard<std::mutex> lock(global_mutex());
     150          117 :             if(auto* p = global().buckets[idx].pop(local().buckets[idx]))
     151            0 :                 return p;
     152          117 :         }
     153              : 
     154          117 :         return ::operator new(rounded);
     155              :     }
     156              : 
     157              :     void
     158         4753 :     do_deallocate(void* p, std::size_t bytes, std::size_t) override
     159              :     {
     160         4753 :         std::size_t rounded = round_up_pow2(bytes);
     161         4753 :         std::size_t idx = get_class_index(rounded);
     162              : 
     163         4753 :         if(idx >= num_classes)
     164              :         {
     165           80 :             ::operator delete(p);
     166           80 :             return;
     167              :         }
     168              : 
     169         4673 :         if(local().buckets[idx].push(p))
     170         4669 :             return;
     171              : 
     172              :         {
     173            4 :             std::lock_guard<std::mutex> lock(global_mutex());
     174            4 :             if(global().buckets[idx].push(p))
     175            4 :                 return;
     176            4 :         }
     177              : 
     178            0 :         ::operator delete(p);
     179              :     }
     180              : 
     181              :     bool
     182            0 :     do_is_equal(const memory_resource& other) const noexcept override
     183              :     {
     184            0 :         return this == &other;
     185              :     }
     186              : };
     187              : 
     188              : /** Returns pointer to the default recycling memory resource.
     189              : 
     190              :     The returned pointer is valid for the lifetime of the program.
     191              :     This is the default allocator used by run_async.
     192              : 
     193              :     @return Pointer to the recycling memory resource.
     194              : 
     195              :     @see recycling_memory_resource
     196              :     @see run_async
     197              : */
     198              : BOOST_CAPY_DECL
     199              : std::pmr::memory_resource*
     200              : get_recycling_memory_resource() noexcept;
     201              : 
     202              : } // namespace capy
     203              : } // namespace boost
     204              : 
     205              : #endif
        

Generated by: LCOV version 2.3