Back to home page

EIC code displayed by LXR

 
 

    


Warning, file /include/Gaudi/Arena/Monotonic.h was not indexed or was modified since last indexation (in which case cross-reference links may be missing, inaccurate or erroneous).

0001 /***********************************************************************************\
0002 * (c) Copyright 2019-2022 CERN for the benefit of the LHCb and ATLAS collaborations *
0003 *                                                                                   *
0004 * This software is distributed under the terms of the Apache version 2 licence,     *
0005 * copied verbatim in the file "LICENSE".                                            *
0006 *                                                                                   *
0007 * In applying this licence, CERN does not waive the privileges and immunities       *
0008 * granted to it by virtue of its status as an Intergovernmental Organization        *
0009 * or submit itself to any jurisdiction.                                             *
0010 \***********************************************************************************/
0011 #pragma once
0012 
0013 #include <Gaudi/Allocator/Arena.h>
0014 #include <GaudiKernel/Kernel.h>
0015 #include <boost/container/small_vector.hpp>
0016 #include <cstddef>
0017 #include <gsl/span>
0018 #include <numeric>
0019 #include <utility>
0020 
0021 namespace Gaudi::Arena {
0022   namespace details {
0023     template <std::size_t Alignment>
0024     constexpr std::size_t align_up( std::size_t n ) {
0025       return ( n + ( Alignment - 1 ) ) & ~( Alignment - 1 );
0026     }
0027   } // namespace details
0028 
0029   /** @class Monotonic
0030    *  @brief A fast memory arena that does not track deallocations.
0031    *
0032    *  This is a memory arena suitable for use with Gaudi::Allocators::Arena.
0033    *  It allocates memory from an upstream resource in blocks of geometrically
0034    *  increasing size and serves allocation requests from those blocks.
0035    *  Deallocations are not tracked, so the memory footprint of a Monotonic
0036    *  arena increases monotonically until either it is destroyed or its reset()
0037    *  method is called.
0038    *  All requests are served with alignment specified in the template parameter.
0039    *
0040    *  @todo Efficiently support stateful upstream allocators, probably by putting an
0041    *        instance of the upstream allocator in a boost::compressed_pair.
0042    *  @todo Use the given UpstreamAllocator to serve dynamic allocations required by
0043    *        boost::container::small_vector.
0044    */
0045   template <std::size_t Alignment = alignof( std::max_align_t ), typename UpstreamAllocator = std::allocator<std::byte>>
0046   class Monotonic {
0047     // Restriction could be lifted, see @todo above.
0048     static_assert( std::is_empty_v<UpstreamAllocator>, "Stateful upstream allocators are not yet supported." );
0049 
0050     /// Size (in bytes) of the next block to be allocated.
0051     std::size_t m_next_block_size{};
0052 
0053     /// Number of allocation requests served by this arena.
0054     std::size_t m_allocations{ 0 };
0055 
0056     /// Current position in the current block, or nullptr if there is no current block.
0057     std::byte* m_current{ nullptr };
0058 
0059     /// One byte past the end of the current block, or nullptr if it doesn't exist.
0060     std::byte* m_current_end{ nullptr };
0061 
0062     /// All memory blocks owned by this arena.
0063     boost::container::small_vector<gsl::span<std::byte>, 1> m_all_blocks;
0064 
0065     /// Approximate factor by which each block is larger than its predecessor.
0066     static constexpr std::size_t growth_factor = 2;
0067 
0068   public:
0069     static constexpr std::size_t alignment = Alignment;
0070 
0071     /** Construct an arena whose first block have approximately the given size.
0072      *  This constructor does not trigger any allocation.
0073      */
0074     Monotonic( std::size_t next_block_size ) noexcept
0075         : m_next_block_size{ details::align_up<Alignment>( next_block_size ) } {}
0076 
0077     ~Monotonic() noexcept {
0078       for ( auto block : m_all_blocks ) { UpstreamAllocator{}.deallocate( block.data(), block.size() ); }
0079     }
0080 
0081     // Allocators will hold pointers to instances of this class, deleting these
0082     // methods makes it harder to accidentally invalidate those pointers...
0083     Monotonic( Monotonic&& )                 = delete;
0084     Monotonic( Monotonic const& )            = delete;
0085     Monotonic& operator=( Monotonic&& )      = delete;
0086     Monotonic& operator=( Monotonic const& ) = delete;
0087 
0088     /** Return an aligned point to n bytes of memory.
0089      *  This may trigger allocation from the upstream resource.
0090      */
0091     template <std::size_t ReqAlign>
0092     std::byte* allocate( std::size_t n ) {
0093       // If the requested alignment was larger we would need to round up
0094       // m_current -- instead of implementing that, just assert it's not
0095       // the case.
0096       static_assert( ReqAlign <= alignment, "Requested alignment too large for this Gaudi::Arena::Monotonic!" );
0097       // Figure out how many bytes we need to allocate
0098       std::size_t const aligned_n = details::align_up<Alignment>( n );
0099       // Check that we have a current block and this request fits inside it
0100       if ( !m_current || m_current + aligned_n > m_current_end ) {
0101         // Calculate our next block size
0102         auto next_block_size = std::max( m_next_block_size, aligned_n );
0103         // And update the estimate of what comes after that, following a geometric series
0104         m_next_block_size = details::align_up<Alignment>( growth_factor * next_block_size );
0105         // Allocate the new block and mark it as the current one
0106         m_current     = UpstreamAllocator{}.allocate( next_block_size );
0107         m_current_end = m_current + next_block_size;
0108         // Add it to the list of blocks that we'll eventually deallocate
0109         m_all_blocks.emplace_back( m_current, next_block_size );
0110       }
0111       m_allocations++;
0112       return std::exchange( m_current, m_current + aligned_n );
0113     }
0114 
0115     /** Deallocations are not tracked, so this is a no-op!
0116      */
0117     constexpr void deallocate( std::byte*, std::size_t ) noexcept {}
0118 
0119     /** Signal that this arena may start re-using the memory resources.
0120      *  - If the arena owns zero blocks, there is no change.
0121      *  - If the arena owns one block, it will reset to serving future requests from the
0122      *    start of that block.
0123      *  - If the arena owns more than one block, it will deallocate all but the first one
0124      *    and serve future requests from the start of the remaining block.
0125      */
0126     void reset() noexcept {
0127       m_allocations = 0;
0128       if ( !m_all_blocks.empty() ) {
0129         // Only re-use the first block, deallocate any others
0130         if ( m_all_blocks.size() > 1 ) {
0131           for ( std::size_t i = 1; i < m_all_blocks.size(); ++i ) {
0132             UpstreamAllocator{}.deallocate( m_all_blocks[i].data(), m_all_blocks[i].size() );
0133           }
0134           m_all_blocks.resize( 1 );
0135         }
0136         auto reused_block = m_all_blocks.front();
0137         m_current         = reused_block.data();
0138         m_current_end     = m_current + reused_block.size();
0139         m_next_block_size = details::align_up<Alignment>( growth_factor * reused_block.size() );
0140       }
0141     }
0142 
0143     /** Query how much memory is owned by this arena, in bytes.
0144      */
0145     [[nodiscard]] std::size_t capacity() const noexcept {
0146       return std::accumulate( m_all_blocks.begin(), m_all_blocks.end(), 0ul,
0147                               []( std::size_t sum, auto block ) { return sum + block.size(); } );
0148     }
0149 
0150     /** Query how much memory was *used* from this arena, in bytes.
0151      */
0152     [[nodiscard]] std::size_t size() const noexcept { return capacity() - ( m_current_end - m_current ); }
0153 
0154     /** Query how many blocks of memory this arena owns.
0155      */
0156     [[nodiscard]] std::size_t num_blocks() const noexcept { return m_all_blocks.size(); }
0157 
0158     /** Query how many allocations this arena has served.
0159      */
0160     [[nodiscard]] std::size_t num_allocations() const noexcept { return m_allocations; }
0161   };
0162 } // namespace Gaudi::Arena
0163 
0164 namespace Gaudi::Allocator {
0165   /** @class MonotonicArena
0166    *  @brief Shorthand for Gaudi::Allocator::Arena with Gaudi::Arena::Monotonic resource
0167    */
0168   template <typename T, typename DefaultResource = void, std::size_t Alignment = alignof( std::max_align_t ),
0169             typename UpstreamAllocator = std::allocator<std::byte>>
0170   using MonotonicArena =
0171       ::Gaudi::Allocator::Arena<::Gaudi::Arena::Monotonic<Alignment, UpstreamAllocator>, T, DefaultResource>;
0172 } // namespace Gaudi::Allocator