LCOV - code coverage report
Current view: top level - capy/ex - recycling_memory_resource.hpp (source / functions) Coverage Total Hit
Test: coverage_filtered.info Lines: 81.4 % 59 48
Test Date: 2026-02-02 05:00:52 Functions: 90.0 % 10 9

            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 <cstddef>
      16              : #include <memory_resource>
      17              : #include <mutex>
      18              : 
      19              : namespace boost {
      20              : namespace capy {
      21              : 
      22              : /** Recycling memory resource with thread-local and global pools.
      23              : 
      24              :     This memory resource recycles memory blocks to reduce allocation
      25              :     overhead for coroutine frames. It maintains a thread-local pool
      26              :     for fast lock-free access and a global pool for cross-thread
      27              :     block sharing.
      28              : 
      29              :     Blocks are tracked by size to avoid returning undersized blocks.
      30              : 
      31              :     This is the default allocator used by run_async when no allocator
      32              :     is specified.
      33              : 
      34              :     @par Thread Safety
      35              :     Thread-safe. The thread-local pool requires no synchronization.
      36              :     The global pool uses a mutex for cross-thread access.
      37              : 
      38              :     @par Example
      39              :     @code
      40              :     auto* mr = get_recycling_memory_resource();
      41              :     run_async(ex, mr)(my_task());
      42              :     @endcode
      43              : 
      44              :     @see get_recycling_memory_resource
      45              :     @see run_async
      46              : */
      47              : class recycling_memory_resource : public std::pmr::memory_resource
      48              : {
      49              :     struct block
      50              :     {
      51              :         block* next;
      52              :         std::size_t size;
      53              :     };
      54              : 
      55              :     struct global_pool
      56              :     {
      57              :         std::mutex mtx;
      58              :         block* head = nullptr;
      59              : 
      60           23 :         ~global_pool()
      61              :         {
      62           23 :             while(head)
      63              :             {
      64            0 :                 auto p = head;
      65            0 :                 head = head->next;
      66            0 :                 ::operator delete(p);
      67              :             }
      68           23 :         }
      69              : 
      70              :         void push(block* b)
      71              :         {
      72              :             std::lock_guard<std::mutex> lock(mtx);
      73              :             b->next = head;
      74              :             head = b;
      75              :         }
      76              : 
      77           94 :         block* pop(std::size_t n)
      78              :         {
      79           94 :             std::lock_guard<std::mutex> lock(mtx);
      80           94 :             block** pp = &head;
      81           94 :             while(*pp)
      82              :             {
      83            0 :                 if((*pp)->size >= n + sizeof(block))
      84              :                 {
      85            0 :                     block* p = *pp;
      86            0 :                     *pp = p->next;
      87            0 :                     return p;
      88              :                 }
      89            0 :                 pp = &(*pp)->next;
      90              :             }
      91           94 :             return nullptr;
      92           94 :         }
      93              :     };
      94              : 
      95              :     struct local_pool
      96              :     {
      97              :         block* head = nullptr;
      98              : 
      99           24 :         ~local_pool()
     100              :         {
     101          118 :             while(head)
     102              :             {
     103           94 :                 auto p = head;
     104           94 :                 head = head->next;
     105           94 :                 ::operator delete(p);
     106              :             }
     107           24 :         }
     108              : 
     109         4389 :         void push(block* b)
     110              :         {
     111         4389 :             b->next = head;
     112         4389 :             head = b;
     113         4389 :         }
     114              : 
     115         4389 :         block* pop(std::size_t n)
     116              :         {
     117         4389 :             block** pp = &head;
     118         5960 :             while(*pp)
     119              :             {
     120         5866 :                 if((*pp)->size >= n + sizeof(block))
     121              :                 {
     122         4295 :                     block* p = *pp;
     123         4295 :                     *pp = p->next;
     124         4295 :                     return p;
     125              :                 }
     126         1571 :                 pp = &(*pp)->next;
     127              :             }
     128           94 :             return nullptr;
     129              :         }
     130              :     };
     131              : 
     132         8778 :     static local_pool& local()
     133              :     {
     134         8778 :         static thread_local local_pool pool;
     135         8778 :         return pool;
     136              :     }
     137              : 
     138           94 :     static global_pool& global()
     139              :     {
     140           94 :         static global_pool pool;
     141           94 :         return pool;
     142              :     }
     143              : 
     144              : protected:
     145              :     void*
     146         4389 :     do_allocate(std::size_t bytes, std::size_t) override
     147              :     {
     148         4389 :         std::size_t total = bytes + sizeof(block);
     149              : 
     150         4389 :         if(auto* b = local().pop(bytes))
     151         4295 :             return static_cast<char*>(static_cast<void*>(b)) + sizeof(block);
     152              : 
     153           94 :         if(auto* b = global().pop(bytes))
     154            0 :             return static_cast<char*>(static_cast<void*>(b)) + sizeof(block);
     155              : 
     156           94 :         auto* b = static_cast<block*>(::operator new(total));
     157           94 :         b->next = nullptr;
     158           94 :         b->size = total;
     159           94 :         return static_cast<char*>(static_cast<void*>(b)) + sizeof(block);
     160              :     }
     161              : 
     162              :     void
     163         4389 :     do_deallocate(void* p, std::size_t, std::size_t) override
     164              :     {
     165         4389 :         auto* b = static_cast<block*>(
     166              :             static_cast<void*>(static_cast<char*>(p) - sizeof(block)));
     167         4389 :         b->next = nullptr;
     168         4389 :         local().push(b);
     169         4389 :     }
     170              : 
     171              :     bool
     172            0 :     do_is_equal(const memory_resource& other) const noexcept override
     173              :     {
     174            0 :         return this == &other;
     175              :     }
     176              : };
     177              : 
     178              : /** Returns pointer to the default recycling memory resource.
     179              : 
     180              :     The returned pointer is valid for the lifetime of the program.
     181              :     This is the default allocator used by run_async.
     182              : 
     183              :     @return Pointer to the recycling memory resource.
     184              : 
     185              :     @see recycling_memory_resource
     186              :     @see run_async
     187              : */
     188              : BOOST_CAPY_DECL
     189              : std::pmr::memory_resource*
     190              : get_recycling_memory_resource() noexcept;
     191              : 
     192              : } // namespace capy
     193              : } // namespace boost
     194              : 
     195              : #endif
        

Generated by: LCOV version 2.3