diff --git a/libstdc++-v3/ChangeLog b/libstdc++-v3/ChangeLog index fe28f52bc77..0231623db83 100644 --- a/libstdc++-v3/ChangeLog +++ b/libstdc++-v3/ChangeLog @@ -1,3 +1,35 @@ +2018-11-06 Jonathan Wakely + + Implement std::pmr::unsynchronized_pool_resource + * config/abi/pre/gnu.ver: Add new symbols. + * include/std/memory_resource (std::pmr::__pool_resource): New class. + (std::pmr::unsynchronized_pool_resource): New class. + * src/c++17/Makefile.am: Add -fimplicit-templates to flags for + memory_resource.cc + * src/c++17/Makefile.in: Regenerate. + * src/c++17/memory_resource.cc (bitset, chunk, big_block): New + internal classes. + (__pool_resource::_Pool): Define new class. + (munge_options, pool_index, select_num_pools): New internal functions. + (__pool_resource::__pool_resource, __pool_resource::~__pool_resource) + (__pool_resource::allocate, __pool_resource::deallocate) + (__pool_resource::_M_alloc_pools): Define member functions. + (unsynchronized_pool_resource::unsynchronized_pool_resource) + (unsynchronized_pool_resource::~unsynchronized_pool_resource) + (unsynchronized_pool_resource::release) + (unsynchronized_pool_resource::_M_find_pool) + (unsynchronized_pool_resource::do_allocate) + (unsynchronized_pool_resource::do_deallocate): Define member + functions. + * testsuite/20_util/unsynchronized_pool_resource/allocate.cc: New + test. + * testsuite/20_util/unsynchronized_pool_resource/is_equal.cc: New + test. + * testsuite/20_util/unsynchronized_pool_resource/options.cc: New + test. + * testsuite/20_util/unsynchronized_pool_resource/release.cc: New + test. + 2018-11-06 John Bytheway PR libstdc++/87872 diff --git a/libstdc++-v3/config/abi/pre/gnu.ver b/libstdc++-v3/config/abi/pre/gnu.ver index e8cd286ef0c..b55038b8845 100644 --- a/libstdc++-v3/config/abi/pre/gnu.ver +++ b/libstdc++-v3/config/abi/pre/gnu.ver @@ -2055,6 +2055,15 @@ GLIBCXX_3.4.26 { _ZNSt13basic_filebufI[cw]St11char_traitsI[cw]EE4openEPKwSt13_Ios_Openmode; _ZN11__gnu_debug25_Safe_local_iterator_base16_M_attach_singleEPNS_19_Safe_sequence_baseEb; + + # members + _ZTINSt3pmr28unsynchronized_pool_resourceE; + _ZNSt3pmr28unsynchronized_pool_resourceC[12]ERKNS_12pool_optionsEPNS_15memory_resourceE; + _ZNSt3pmr28unsynchronized_pool_resourceD[12]Ev; + _ZNSt3pmr28unsynchronized_pool_resource7releaseEv; + _ZNSt3pmr28unsynchronized_pool_resource11do_allocateEmm; + _ZNSt3pmr28unsynchronized_pool_resource13do_deallocateEPvmm; + } GLIBCXX_3.4.25; # Symbols in the support library (libsupc++) have their own tag. diff --git a/libstdc++-v3/include/std/memory_resource b/libstdc++-v3/include/std/memory_resource index 7dc35ae723d..40486af82fe 100644 --- a/libstdc++-v3/include/std/memory_resource +++ b/libstdc++-v3/include/std/memory_resource @@ -33,9 +33,9 @@ #if __cplusplus >= 201703L -#include // __ceil2, __log2p1 #include // align, allocator_arg_t, __uses_alloc #include // pair, index_sequence +#include // vector #include // size_t, max_align_t #include @@ -296,8 +296,107 @@ namespace pmr size_t largest_required_pool_block = 0; }; - // TODO class synchronized_pool_resource; - // TODO class unsynchronized_pool_resource; + // Common implementation details for unsynchronized/synchronized pool resources. + class __pool_resource + { + friend class synchronized_pool_resource; + friend class unsynchronized_pool_resource; + + __pool_resource(const pool_options& __opts, memory_resource* __upstream); + + ~__pool_resource(); + + __pool_resource(const __pool_resource&) = delete; + __pool_resource& operator=(const __pool_resource&) = delete; + + // Allocate a large unpooled block. + void* + allocate(size_t __bytes, size_t __alignment); + + // Deallocate a large unpooled block. + void + deallocate(void* __p, size_t __bytes, size_t __alignment); + + + // Deallocate unpooled memory. + void release() noexcept; + + memory_resource* resource() const noexcept + { return _M_unpooled.get_allocator().resource(); } + + struct _Pool; + + _Pool* _M_alloc_pools(); + + const pool_options _M_opts; + + struct _BigBlock; + // Collection of blocks too big for any pool, sorted by address. + // This also stores the only copy of the upstream memory resource pointer. + pmr::vector<_BigBlock> _M_unpooled; + + const int _M_npools; + }; + + // TODO class synchronized_pool_resource + + /// A non-thread-safe memory resource that manages pools of fixed-size blocks. + class unsynchronized_pool_resource : public memory_resource + { + public: + [[__gnu__::__nonnull__]] + unsynchronized_pool_resource(const pool_options& __opts, + memory_resource* __upstream); + + unsynchronized_pool_resource() + : unsynchronized_pool_resource(pool_options(), get_default_resource()) + { } + + [[__gnu__::__nonnull__]] + explicit + unsynchronized_pool_resource(memory_resource* __upstream) + : unsynchronized_pool_resource(pool_options(), __upstream) + { } + + explicit + unsynchronized_pool_resource(const pool_options& __opts) + : unsynchronized_pool_resource(__opts, get_default_resource()) { } + + unsynchronized_pool_resource(const unsynchronized_pool_resource&) = delete; + + virtual ~unsynchronized_pool_resource(); + + unsynchronized_pool_resource& + operator=(const unsynchronized_pool_resource&) = delete; + + void release(); + + [[__gnu__::__returns_nonnull__]] + memory_resource* + upstream_resource() const noexcept + { return _M_impl.resource(); } + + pool_options options() const noexcept { return _M_impl._M_opts; } + + protected: + void* + do_allocate(size_t __bytes, size_t __alignment) override; + + void + do_deallocate(void* __p, size_t __bytes, size_t __alignment) override; + + bool + do_is_equal(const memory_resource& __other) const noexcept override + { return this == &__other; } + + private: + using _Pool = __pool_resource::_Pool; + + auto _M_find_pool(size_t) noexcept; + + __pool_resource _M_impl; + _Pool* _M_pools = nullptr; + }; class monotonic_buffer_resource : public memory_resource { diff --git a/libstdc++-v3/src/c++17/Makefile.am b/libstdc++-v3/src/c++17/Makefile.am index 21b64b52dc2..c748f50be16 100644 --- a/libstdc++-v3/src/c++17/Makefile.am +++ b/libstdc++-v3/src/c++17/Makefile.am @@ -63,6 +63,11 @@ AM_CXXFLAGS = \ AM_MAKEFLAGS = \ "gxx_include_dir=$(gxx_include_dir)" +memory_resource.lo: memory_resource.cc + $(LTCXXCOMPILE) -fimplicit-templates -c $< -o $@ +memory_resource.o: memory_resource.cc + $(CXXCOMPILE) -fimplicit-templates -c $< -o $@ + # Libtool notes # 1) In general, libtool expects an argument such as `--tag=CXX' when diff --git a/libstdc++-v3/src/c++17/Makefile.in b/libstdc++-v3/src/c++17/Makefile.in index efa4fd9ef73..287b4b8bb69 100644 --- a/libstdc++-v3/src/c++17/Makefile.in +++ b/libstdc++-v3/src/c++17/Makefile.in @@ -731,6 +731,11 @@ uninstall-am: vpath % $(top_srcdir)/src/c++17 +memory_resource.lo: memory_resource.cc + $(LTCXXCOMPILE) -fimplicit-templates -c $< -o $@ +memory_resource.o: memory_resource.cc + $(CXXCOMPILE) -fimplicit-templates -c $< -o $@ + # Tell versions [3.59,3.63) of GNU make to not export all variables. # Otherwise a system limit (for SysV at least) may be exceeded. .NOEXPORT: diff --git a/libstdc++-v3/src/c++17/memory_resource.cc b/libstdc++-v3/src/c++17/memory_resource.cc index aa82813e645..781bdada381 100644 --- a/libstdc++-v3/src/c++17/memory_resource.cc +++ b/libstdc++-v3/src/c++17/memory_resource.cc @@ -23,7 +23,9 @@ // . #include +#include // lower_bound, rotate #include +#include // __ceil2, __log2p1 #include #if ATOMIC_POINTER_LOCK_FREE != 2 # include // std::mutex, std::lock_guard @@ -246,8 +248,788 @@ namespace pmr _Chunk::release(_M_head, _M_upstream); } + // Helper types for synchronized_pool_resource & unsynchronized_pool_resource + + namespace { + + // Simple bitset with runtime size. Tracks used blocks in a pooled chunk. + struct bitset + { + using word = uint64_t; + using size_type = uint32_t; + + static constexpr unsigned bits_per_word = numeric_limits::digits; + + // The bitset does not own p + bitset(void* p, size_type num_blocks) + : _M_words(static_cast(p)), _M_size(num_blocks), + _M_next_word(0) + { + const size_type last_word = num_blocks / bits_per_word; + __builtin_memset(_M_words, 0, last_word * sizeof(*_M_words)); + // Set bits beyond _M_size, so they are not treated as free blocks: + if (const size_type extra_bits = num_blocks % bits_per_word) + _M_words[last_word] = (word)-1 << extra_bits; + __glibcxx_assert( empty() ); + __glibcxx_assert( free() == num_blocks ); + } + + bitset() = default; + ~bitset() = default; + + // Number of blocks + size_t size() const noexcept { return _M_size; } + + // Number of unset bits + size_t free() const noexcept + { + size_t n = 0; + for (size_type i = _M_next_word; i < nwords(); ++i) + n += (bits_per_word - std::__popcount(_M_words[i])); + return n; + } + + // True if all bits are set + bool full() const noexcept { return _M_next_word >= nwords(); } + + // True if size() != 0 and no bits are set. + bool empty() const noexcept + { + if (nwords() == 0) + return false; + if (_M_next_word != 0) + return false; + for (size_type i = 0; i < nwords() - 1; ++i) + if (_M_words[i] != 0) + return false; + word last = _M_words[nwords() - 1]; + if (const size_type extra_bits = size() % bits_per_word) + last <<= (bits_per_word - extra_bits); + return last == 0; + } + + void reset() noexcept + { + _M_words = nullptr; + _M_size = _M_next_word = 0; + } + + bool operator[](size_type n) const noexcept + { + __glibcxx_assert( n < _M_size ); + const size_type wd = n / bits_per_word; + const word bit = word(1) << (n % bits_per_word); + return _M_words[wd] & bit; + } + + size_type find_first_unset() const noexcept + { + for (size_type i = _M_next_word; i < nwords(); ++i) + { + const size_type n = std::__countr_one(_M_words[i]); + if (n < bits_per_word) + return (i * bits_per_word) + n; + } + return size_type(-1); + } + + size_type get_first_unset() noexcept + { + for (size_type i = _M_next_word; i < nwords(); ++i) + { + const size_type n = std::__countr_one(_M_words[i]); + if (n < bits_per_word) + { + const word bit = word(1) << n; + _M_words[i] |= bit; + if (i == _M_next_word) + { + while (_M_words[_M_next_word] == word(-1) + && ++_M_next_word != nwords()) + { } + } + return (i * bits_per_word) + n; + } + } + return size_type(-1); + } + + void set(size_type n) noexcept + { + __glibcxx_assert( n < _M_size ); + const size_type wd = n / bits_per_word; + const word bit = word(1) << (n % bits_per_word); + _M_words[wd] |= bit; + if (wd == _M_next_word) + { + while (_M_words[_M_next_word] == word(-1) + && ++_M_next_word != nwords()) + { } + } + } + + void clear(size_type n) noexcept + { + __glibcxx_assert( n < _M_size ); + const size_type wd = n / bits_per_word; + const word bit = word(1) << (n % bits_per_word); + _M_words[wd] &= ~bit; + if (wd < _M_next_word) + _M_next_word = wd; + } + + void swap(bitset& b) noexcept + { + std::swap(_M_words, b._M_words); + size_type tmp = _M_size; + _M_size = b._M_size; + b._M_size = tmp; + tmp = _M_next_word; + _M_next_word = b._M_next_word; + b._M_next_word = tmp; + } + + size_type nwords() const noexcept + { return (_M_size + bits_per_word - 1) / bits_per_word; } + + // Maximum value that can be stored in bitset::_M_size member (approx 500k) + static constexpr size_t max_blocks_per_chunk() noexcept + { return (1ull << _S_size_digits) - 1; } + + word* data() const noexcept { return _M_words; } + + private: + static constexpr unsigned _S_size_digits + = (numeric_limits::digits + + std::__log2p1(bits_per_word) - 1) / 2; + + word* _M_words = nullptr; + // Number of blocks represented by the bitset: + size_type _M_size : _S_size_digits; + // Index of the first word with unset bits: + size_type _M_next_word : numeric_limits::digits - _S_size_digits; + }; + + // A "chunk" belonging to a pool. + // A chunk contains many blocks of the same size. + // Derived from bitset to reuse its tail-padding. + struct chunk : bitset + { + chunk() = default; + + // p points to the start of a chunk of size bytes in length. + // The chunk has space for n blocks, followed by a bitset of size n + // that begins at address words. + // This object does not own p or words, the caller will free it. + chunk(void* p, size_t bytes, void* words, size_t n) + : bitset(words, n), + _M_bytes(bytes), + _M_p(static_cast(p)) + { } + + chunk(chunk&& c) noexcept + : bitset(std::move(c)), _M_bytes(c._M_bytes), _M_p(c._M_p) + { + c._M_bytes = 0; + c._M_p = nullptr; + c.reset(); + } + + chunk& operator=(chunk&& c) noexcept + { + swap(c); + return *this; + } + + // Allocated size of chunk: + unsigned _M_bytes = 0; + // Start of allocated chunk: + std::byte* _M_p = nullptr; + + // True if there are free blocks in this chunk + using bitset::full; + // Number of blocks in this chunk + using bitset::size; + + // Determine if block with address p and size block_size + // is contained within this chunk. + bool owns(void* p, size_t block_size) + { + std::less_equal less_equal; + return less_equal(reinterpret_cast(_M_p), + reinterpret_cast(p)) + && less_equal(reinterpret_cast(p) + block_size, + reinterpret_cast(bitset::data())); + } + + // Allocate next available block of block_size bytes from this chunk. + void* reserve(size_t block_size) noexcept + { + const size_type n = get_first_unset(); + if (n == size_type(-1)) + return nullptr; + return _M_p + (n * block_size); + } + + // Deallocate a single block of block_size bytes + void release(void* vp, size_t block_size) + { + __glibcxx_assert( owns(vp, block_size) ); + const size_t offset = static_cast(vp) - _M_p; + // Pointer is correctly aligned for a block in this chunk: + __glibcxx_assert( (offset % block_size) == 0 ); + // Block has been allocated: + __glibcxx_assert( (*this)[offset / block_size] == true ); + bitset::clear(offset / block_size); + } + + // Deallocate a single block if it belongs to this chunk. + bool try_release(void* p, size_t block_size) + { + if (!owns(p, block_size)) + return false; + release(p, block_size); + return true; + } + + void swap(chunk& c) noexcept + { + std::swap(_M_bytes, c._M_bytes); + std::swap(_M_p, c._M_p); + bitset::swap(c); + } + + bool operator<(const chunk& c) const noexcept + { return std::less{}(_M_p, c._M_p); } + + friend void swap(chunk& l, chunk& r) { l.swap(r); } + + friend bool operator<(const void* p, const chunk& c) noexcept + { return std::less{}(p, c._M_p); } + }; + +#ifdef __LP64__ + // TODO pad up to 4*sizeof(void*) to avoid splitting across cache lines? + static_assert(sizeof(chunk) == (3 * sizeof(void*)), ""); +#else + static_assert(sizeof(chunk) == (4 * sizeof(void*)), ""); +#endif + + // An oversized allocation that doesn't fit in a pool. + struct big_block + { + static constexpr unsigned _S_alignbits + = std::__log2p1((unsigned)numeric_limits::digits) - 1; + static constexpr unsigned _S_sizebits + = numeric_limits::digits - _S_alignbits; + // The maximum value that can be stored in _S_size + static constexpr size_t all_ones = (1ul << _S_sizebits) - 1u; + // The minimum size of a big block + static constexpr size_t min = 1u << _S_alignbits; + + big_block(size_t bytes, size_t alignment) + : _M_size((bytes + min - 1u) >> _S_alignbits), + _M_align_exp(std::__log2p1(alignment) - 1u) + { + if (__builtin_expect(std::__countl_one(bytes) == _S_sizebits, false)) + _M_size = all_ones; + } + + void* pointer = nullptr; + size_t _M_size : numeric_limits::digits - _S_alignbits; + size_t _M_align_exp : _S_alignbits; + + size_t size() const noexcept + { + if (__builtin_expect(_M_size == all_ones, false)) + return (size_t)-1; + else + return _M_size << _S_alignbits; + } + + size_t align() const noexcept { return 1ul << _M_align_exp; } + + friend bool operator<(void* p, const big_block& b) noexcept + { return less{}(p, b.pointer); } + + friend bool operator<(const big_block& b, void* p) noexcept + { return less{}(b.pointer, p); } + }; + + static_assert(sizeof(big_block) == (2 * sizeof(void*))); + + } // namespace + + // A pool that serves blocks of a particular size. + // Each pool manages a number of chunks. + // When a pool is full it is replenished by allocating another chunk. + struct __pool_resource::_Pool + { + // Smallest supported block size + static constexpr unsigned _S_min_block + = std::max(sizeof(void*), alignof(bitset::word)); + + _Pool(size_t __block_size, size_t __blocks_per_chunk) + : _M_chunks(), + _M_block_sz(__block_size), + _M_blocks_per_chunk(__blocks_per_chunk) + { + __glibcxx_assert(block_size() == __block_size); + } + + // Must call release(r) before destruction! + ~_Pool() { __glibcxx_assert(_M_chunks.empty()); } + + _Pool(_Pool&&) noexcept = default; + _Pool& operator=(_Pool&&) noexcept = default; + + // Size of blocks in this pool + size_t block_size() const noexcept +#if POW2_BLKSZ + { return _S_min_block << _M_blksize_mul; } +#else + { return _M_block_sz; } +#endif + + // Allocate a block if the pool is not full, otherwise return null. + void* try_allocate() noexcept + { + const size_t blocksz = block_size(); + if (!_M_chunks.empty()) + { + auto& last = _M_chunks.back(); + if (void* p = last.reserve(blocksz)) + return p; + // TODO last is full, so move another chunk to the back instead? + for (auto it = _M_chunks.begin(); it != &last; ++it) + if (void* p = it->reserve(blocksz)) + return p; + } + return nullptr; + } + + // Allocate a block from the pool, replenishing from upstream if needed. + void* allocate(memory_resource* r, const pool_options& opts) + { + if (void* p = try_allocate()) + return p; + replenish(r, opts); + return _M_chunks.back().reserve(block_size()); + } + + // Return a block to the pool. + bool deallocate(memory_resource*, void* p) + { + const size_t blocksz = block_size(); + if (__builtin_expect(!_M_chunks.empty(), true)) + { + auto& last = _M_chunks.back(); + if (last.try_release(p, blocksz)) + return true; + auto it = std::upper_bound(_M_chunks.begin(), &last, p); + if (it != _M_chunks.begin()) + { + it--; + if (it->try_release(p, blocksz)) + // If chunk is empty could return to upstream, but we don't + // currently do that. Pools only increase in size. + return true; + } + } + return false; + } + + void replenish(memory_resource* __r, const pool_options& __opts) + { + using word = chunk::word; + const size_t __blocks + = std::min(__opts.max_blocks_per_chunk, _M_blocks_per_chunk); + const auto __bits = chunk::bits_per_word; + const size_t __words = (__blocks + __bits - 1) / __bits; + const size_t __block_size = block_size(); + size_t __bytes = __blocks * __block_size + __words * sizeof(word); + size_t __alignment = std::__ceil2(__block_size); + void* __p = __r->allocate(__bytes, __alignment); + __try + { + size_t __n = __blocks * __block_size; + void* __pwords = static_cast(__p) + __n; + _M_chunks.insert(chunk(__p, __bytes, __pwords, __blocks), __r); + } + __catch (...) + { + __r->deallocate(__p, __bytes, __alignment); + } + if (_M_blocks_per_chunk < __opts.max_blocks_per_chunk) + _M_blocks_per_chunk *= 2; + } + + void release(memory_resource* __r) + { + const size_t __alignment = std::__ceil2(block_size()); + for (auto& __c : _M_chunks) + if (__c._M_p) + __r->deallocate(__c._M_p, __c._M_bytes, __alignment); + _M_chunks.clear(__r); + } + + // A "resourceless vector" instead of pmr::vector, to save space. + // All resize operations need to be passed a memory resource, which + // obviously needs to be the same one every time. + // Chunks are kept sorted by address of their first block, except for + // the most recently-allocated Chunk which is at the end of the vector. + struct vector + { + using value_type = chunk; + using size_type = unsigned; + using iterator = value_type*; + + // A vector owns its data pointer but not memory held by its elements. + chunk* data = nullptr; + size_type size = 0; + size_type capacity = 0; + + vector() = default; + + vector(size_type __n, memory_resource* __r) + : data(polymorphic_allocator(__r).allocate(__n)), + capacity(__n) + { } + + // Must call clear(r) before destruction! + ~vector() { __glibcxx_assert(data == nullptr); } + + vector(vector&& __rval) noexcept + : data(__rval.data), size(__rval.size), capacity(__rval.capacity) + { + __rval.data = nullptr; + __rval.capacity = __rval.size = 0; + } + + vector& operator=(vector&& __rval) noexcept + { + __glibcxx_assert(data == nullptr); + data = __rval.data; + size = __rval.size; + capacity = __rval.capacity; + __rval.data = nullptr; + __rval.capacity = __rval.size = 0; + return *this; + } + + // void resize(size_type __n, memory_resource* __r); + // void reserve(size_type __n, memory_resource* __r); + + void clear(memory_resource* __r) + { + if (!data) + return; + // Chunks must be individually freed before clearing the vector. + std::destroy(begin(), end()); + polymorphic_allocator(__r).deallocate(data, capacity); + data = nullptr; + capacity = size = 0; + } + + // Sort existing elements then insert new one at the end. + iterator insert(chunk&& c, memory_resource* r) + { + if (size < capacity) + { + if (size > 1) + { + auto mid = end() - 1; + std::rotate(std::lower_bound(begin(), mid, *mid), mid, end()); + } + } + else if (size > 0) + { + polymorphic_allocator __alloc(r); + auto __mid = std::lower_bound(begin(), end() - 1, back()); + auto __p = __alloc.allocate(capacity * 1.5); + // move [begin,__mid) to new storage + auto __p2 = std::move(begin(), __mid, __p); + // move end-1 to new storage + *__p2 = std::move(back()); + // move [__mid,end-1) to new storage + std::move(__mid, end() - 1, ++__p2); + std::destroy(begin(), end()); + __alloc.deallocate(data, capacity); + data = __p; + capacity *= 1.5; + } + else + { + polymorphic_allocator __alloc(r); + data = __alloc.allocate(capacity = 8); + } + auto back = ::new (data + size) chunk(std::move(c)); + __glibcxx_assert(std::is_sorted(begin(), back)); + ++size; + return back; + } + + iterator begin() const { return data; } + iterator end() const { return data + size; } + + bool empty() const noexcept { return size == 0; } + + value_type& back() { return data[size - 1]; } + }; + + vector _M_chunks; + unsigned _M_block_sz; // size of blocks allocated from this pool + unsigned _M_blocks_per_chunk; // number of blocks to allocate next + }; + + // An oversized allocation that doesn't fit in a pool. + struct __pool_resource::_BigBlock : big_block + { + using big_block::big_block; + }; + + namespace { + + pool_options + munge_options(pool_options opts) + { + // The values in the returned struct may differ from those supplied + // to the pool resource constructor in that values of zero will be + // replaced with implementation-defined defaults, and sizes may be + // rounded to unspecified granularity. + + // Absolute maximum. Each pool might have a smaller maximum. + if (opts.max_blocks_per_chunk == 0) + { + opts.max_blocks_per_chunk = 1024 * 10; // TODO a good default? + } + else + { + // TODO round to preferred granularity ? + } + + if (opts.max_blocks_per_chunk > chunk::max_blocks_per_chunk()) + { + opts.max_blocks_per_chunk = chunk::max_blocks_per_chunk(); + } + + // Absolute minimum. Likely to be much larger in practice. + if (opts.largest_required_pool_block == 0) + { + opts.largest_required_pool_block = 4096; // TODO a good default? + } + else + { + // TODO round to preferred granularity ? + } + + if (opts.largest_required_pool_block < big_block::min) + { + opts.largest_required_pool_block = big_block::min; + } + return opts; + } + + const size_t pool_sizes[] = { + 8, 16, 24, + 32, 48, + 64, 80, 96, 112, + 128, 192, + 256, 320, 384, 448, + 512, 768, + 1024, 1536, + 2048, 3072, + 1<<12, 1<<13, 1<<14, 1<<15, 1<<16, 1<<17, + 1<<20, 1<<21, 1<<22 // 4MB should be enough for anybody + }; + + inline int + pool_index(size_t block_size, int npools) + { + auto p = std::lower_bound(pool_sizes, pool_sizes + npools, block_size); + int n = p - pool_sizes; + if (n != npools) + return n; + return -1; + } + + inline int + select_num_pools(const pool_options& opts) + { + auto p = std::lower_bound(std::begin(pool_sizes), std::end(pool_sizes), + opts.largest_required_pool_block); + if (int npools = p - std::begin(pool_sizes)) + return npools; + return 1; + } + + } // namespace + + __pool_resource:: + __pool_resource(const pool_options& opts, memory_resource* upstream) + : _M_opts(munge_options(opts)), _M_unpooled(upstream), + _M_npools(select_num_pools(_M_opts)) + { } + + __pool_resource::~__pool_resource() { release(); } + + void + __pool_resource::release() noexcept + { + memory_resource* res = resource(); + // deallocate oversize allocations + for (auto& b : _M_unpooled) + res->deallocate(b.pointer, b.size(), b.align()); + pmr::vector<_BigBlock>{res}.swap(_M_unpooled); + } + + void* + __pool_resource::allocate(size_t bytes, size_t alignment) + { + auto& b = _M_unpooled.emplace_back(bytes, alignment); + __try { + void* p = resource()->allocate(b.size(), alignment); + b.pointer = p; + if (_M_unpooled.size() > 1) + { + const auto mid = _M_unpooled.end() - 1; + // move to right position in vector + std::rotate(std::lower_bound(_M_unpooled.begin(), mid, p), + mid, _M_unpooled.end()); + } + return p; + } __catch(...) { + _M_unpooled.pop_back(); + __throw_exception_again; + } + } + + void + __pool_resource::deallocate(void* p, size_t bytes [[maybe_unused]], + size_t alignment [[maybe_unused]]) + { + const auto it + = std::lower_bound(_M_unpooled.begin(), _M_unpooled.end(), p); + __glibcxx_assert(it != _M_unpooled.end() && it->pointer == p); + if (it != _M_unpooled.end() && it->pointer == p) // [[likely]] + { + const auto b = *it; + __glibcxx_assert(b.size() == bytes); + __glibcxx_assert(b.align() == alignment); + _M_unpooled.erase(it); + resource()->deallocate(p, b.size(), b.align()); + } + } + + // Create array of pools, allocated from upstream resource. + auto + __pool_resource::_M_alloc_pools() + -> _Pool* + { + polymorphic_allocator<_Pool> alloc{resource()}; + _Pool* p = alloc.allocate(_M_npools); + for (int i = 0; i < _M_npools; ++i) + { + const size_t block_size = pool_sizes[i]; + // Decide on initial number of blocks per chunk. + // Always have at least 16 blocks per chunk: + const size_t min_blocks_per_chunk = 16; + // But for smaller blocks, use a larger initial size: + size_t blocks_per_chunk + = std::max(1024 / block_size, min_blocks_per_chunk); + // But don't exceed the requested max_blocks_per_chunk: + blocks_per_chunk + = std::min(blocks_per_chunk, _M_opts.max_blocks_per_chunk); + // Allow space for bitset to track which blocks are used/unused: + blocks_per_chunk *= 1 - 1.0 / (__CHAR_BIT__ * block_size); + // Construct a _Pool for the given block size and initial chunk size: + alloc.construct(p + i, block_size, blocks_per_chunk); + } + return p; + } + + // unsynchronized_pool_resource member functions + + // Constructor + unsynchronized_pool_resource:: + unsynchronized_pool_resource(const pool_options& opts, + memory_resource* upstream) + : _M_impl(opts, upstream), _M_pools(_M_impl._M_alloc_pools()) + { } + + // Destructor + unsynchronized_pool_resource::~unsynchronized_pool_resource() + { release(); } + + // Return all memory to upstream resource. + void + unsynchronized_pool_resource::release() + { + // release pooled memory + if (_M_pools) + { + memory_resource* res = upstream_resource(); + polymorphic_allocator<_Pool> alloc{res}; + for (int i = 0; i < _M_impl._M_npools; ++i) + { + _M_pools[i].release(res); + alloc.destroy(_M_pools + i); + } + alloc.deallocate(_M_pools, _M_impl._M_npools); + _M_pools = nullptr; + } + + // release unpooled memory + _M_impl.release(); + } + + // Find the right pool for a block of size block_size. + auto + unsynchronized_pool_resource::_M_find_pool(size_t block_size) noexcept + { + __pool_resource::_Pool* pool = nullptr; + if (_M_pools) // [[likely]] + { + int index = pool_index(block_size, _M_impl._M_npools); + if (index != -1) + pool = _M_pools + index; + } + return pool; + } + + // Override for memory_resource::do_allocate + void* + unsynchronized_pool_resource::do_allocate(size_t bytes, size_t alignment) + { + const auto block_size = std::max(bytes, alignment); + if (block_size <= _M_impl._M_opts.largest_required_pool_block) + { + // Recreate pools if release() has been called: + if (__builtin_expect(_M_pools == nullptr, false)) + _M_pools = _M_impl._M_alloc_pools(); + if (auto pool = _M_find_pool(block_size)) + return pool->allocate(upstream_resource(), _M_impl._M_opts); + } + return _M_impl.allocate(bytes, alignment); + } + + // Override for memory_resource::do_deallocate + void + unsynchronized_pool_resource:: + do_deallocate(void* p, size_t bytes, size_t alignment) + { + size_t block_size = std::max(bytes, alignment); + if (block_size <= _M_impl._M_opts.largest_required_pool_block) + { + if (auto pool = _M_find_pool(block_size)) + { + pool->deallocate(upstream_resource(), p); + return; + } + } + _M_impl.deallocate(p, bytes, alignment); + } + } // namespace pmr _GLIBCXX_END_NAMESPACE_VERSION } // namespace std - - diff --git a/libstdc++-v3/testsuite/20_util/unsynchronized_pool_resource/allocate.cc b/libstdc++-v3/testsuite/20_util/unsynchronized_pool_resource/allocate.cc new file mode 100644 index 00000000000..ce9be2f6c49 --- /dev/null +++ b/libstdc++-v3/testsuite/20_util/unsynchronized_pool_resource/allocate.cc @@ -0,0 +1,155 @@ +// Copyright (C) 2018 Free Software Foundation, Inc. +// +// This file is part of the GNU ISO C++ Library. This library is free +// software; you can redistribute it and/or modify it under the +// terms of the GNU General Public License as published by the +// Free Software Foundation; either version 3, or (at your option) +// any later version. + +// This library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License along +// with this library; see the file COPYING3. If not see +// . + +// { dg-options "-std=gnu++17" } +// { dg-do run { target c++17 } } + +#include +#include +#include +#include + +void +test01() +{ + __gnu_test::memory_resource test_mr; + { + std::pmr::unsynchronized_pool_resource r(&test_mr); + void* p1 = r.allocate(1, 1); + VERIFY( p1 != nullptr ); + auto n = test_mr.number_of_active_allocations(); + VERIFY( n > 0 ); + // Ensure memory region can be written to (without corrupting heap!) + std::memset(p1, 0xff, 1); + void* p2 = r.allocate(1, 1); + VERIFY( p2 != nullptr ); + VERIFY( p2 != p1 ); + VERIFY( test_mr.number_of_active_allocations() == n ); + std::memset(p1, 0xff, 1); + r.deallocate(p1, 1, 1); + // Returning single blocks to the pool doesn't return them upstream: + VERIFY( test_mr.number_of_active_allocations() == n ); + r.deallocate(p2, 1, 1); + VERIFY( test_mr.number_of_active_allocations() == n ); + } + VERIFY( test_mr.number_of_active_allocations() == 0 ); +} + +void +test02() +{ + struct nullable_memory_resource : public std::pmr::memory_resource + { + void* + do_allocate(std::size_t bytes, std::size_t alignment) override + { return upstream->allocate(bytes, alignment); } + + void + do_deallocate(void* p, std::size_t bytes, std::size_t alignment) override + { upstream->deallocate(p, bytes, alignment); } + + bool + do_is_equal(const memory_resource& r) const noexcept override + { return &r == this; } + + std::pmr::memory_resource* upstream = std::pmr::get_default_resource(); + }; + + nullable_memory_resource test_mr; + std::pmr::unsynchronized_pool_resource r(&test_mr); + void* p1 = r.allocate(8, 1); + VERIFY( p1 != nullptr ); + std::memset(p1, 0xff, 8); + test_mr.upstream = nullptr; + void* p2 = r.allocate(8, 1); //should not need to replenish + VERIFY( p2 != nullptr ); + VERIFY( p2 != p1 ); + std::memset(p1, 0xff, 8); + r.deallocate(p1, 8, 1); // should not use upstream + r.deallocate(p2, 8, 1); // should not use upstream + + // Destructor will return memory upstream, so restore the upstream resource: + test_mr.upstream = std::pmr::get_default_resource(); +} + +void +test03() +{ + __gnu_test::memory_resource test_mr; + { + std::pmr::unsynchronized_pool_resource r({10, 16}, &test_mr); + std::size_t largest_pool = r.options().largest_required_pool_block; + void* p1 = r.allocate(2 * largest_pool); + VERIFY( p1 != nullptr ); + const std::size_t n = test_mr.number_of_active_allocations(); + // Allocation of pools + allocation of pmr::vector + oversize allocation: + VERIFY( n >= 1 ); + std::memset(p1, 0xff, 2 * largest_pool); + void* p2 = r.allocate(3 * largest_pool); + VERIFY( p2 != nullptr ); + VERIFY( p2 != p1 ); + VERIFY( test_mr.number_of_active_allocations() == n + 1 ); + std::memset(p2, 0xff, 3 * largest_pool); + r.deallocate(p1, 2 * largest_pool); + VERIFY( test_mr.number_of_active_allocations() == n ); + r.deallocate(p2, 3 * largest_pool); + VERIFY( test_mr.number_of_active_allocations() == n - 1 ); + } + VERIFY( test_mr.number_of_active_allocations() == 0 ); + { + std::pmr::unsynchronized_pool_resource r({16, 16}, &test_mr); + (void) r.allocate(2); + (void) r.allocate(8); + (void) r.allocate(16); + (void) r.allocate(2); + (void) r.allocate(8); + (void) r.allocate(16); + (void) r.allocate(2 * r.options().largest_required_pool_block); + VERIFY( test_mr.number_of_active_allocations() != 0 ); + // Destructor calls release() + } + VERIFY( test_mr.number_of_active_allocations() == 0 ); +} + +void +test04() +{ + std::pmr::unsynchronized_pool_resource r({256, 256}); + // Check alignment + void* p1 = r.allocate(2, 64); + VERIFY( (std::uintptr_t)p1 % 64 == 0 ); + void* p2 = r.allocate(2, 128); + VERIFY( (std::uintptr_t)p2 % 128 == 0 ); + void* p3 = r.allocate(2, 256); + VERIFY( (std::uintptr_t)p3 % 256 == 0 ); + const std::size_t largest_pool = r.options().largest_required_pool_block; + void* p4 = r.allocate(2 * largest_pool, 1024); + VERIFY( (std::uintptr_t)p4 % 1024 == 0 ); + r.deallocate(p1, 2, 64); + r.deallocate(p2, 2, 128); + r.deallocate(p3, 2, 256); + r.deallocate(p4, 2 * largest_pool, 1024); +} + +int +main() +{ + test01(); + test02(); + test03(); + test04(); +} diff --git a/libstdc++-v3/testsuite/20_util/unsynchronized_pool_resource/is_equal.cc b/libstdc++-v3/testsuite/20_util/unsynchronized_pool_resource/is_equal.cc new file mode 100644 index 00000000000..789e7bb2724 --- /dev/null +++ b/libstdc++-v3/testsuite/20_util/unsynchronized_pool_resource/is_equal.cc @@ -0,0 +1,38 @@ +// Copyright (C) 2018 Free Software Foundation, Inc. +// +// This file is part of the GNU ISO C++ Library. This library is free +// software; you can redistribute it and/or modify it under the +// terms of the GNU General Public License as published by the +// Free Software Foundation; either version 3, or (at your option) +// any later version. + +// This library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License along +// with this library; see the file COPYING3. If not see +// . + +// { dg-options "-std=gnu++17" } +// { dg-do run { target c++17 } } + +#include +#include + +void +test01() +{ + std::pmr::unsynchronized_pool_resource r1; + VERIFY( r1 == r1 ); + std::pmr::unsynchronized_pool_resource r2; + VERIFY( r1 != r2 ); + VERIFY( r2 != r1 ); +} + +int +main() +{ + test01(); +} diff --git a/libstdc++-v3/testsuite/20_util/unsynchronized_pool_resource/options.cc b/libstdc++-v3/testsuite/20_util/unsynchronized_pool_resource/options.cc new file mode 100644 index 00000000000..bfa8a8ce23c --- /dev/null +++ b/libstdc++-v3/testsuite/20_util/unsynchronized_pool_resource/options.cc @@ -0,0 +1,42 @@ +// Copyright (C) 2018 Free Software Foundation, Inc. +// +// This file is part of the GNU ISO C++ Library. This library is free +// software; you can redistribute it and/or modify it under the +// terms of the GNU General Public License as published by the +// Free Software Foundation; either version 3, or (at your option) +// any later version. + +// This library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License along +// with this library; see the file COPYING3. If not see +// . + +// { dg-options "-std=gnu++17" } +// { dg-do run { target c++17 } } + +#include +#include + +void +test01() +{ + std::pmr::unsynchronized_pool_resource r0; + const std::pmr::pool_options opts = r0.options(); + VERIFY( opts.max_blocks_per_chunk != 0 ); + VERIFY( opts.largest_required_pool_block != 0 ); + + std::pmr::unsynchronized_pool_resource r1(opts); + auto [max_blocks_per_chunk, largest_required_pool_block ] = r1.options(); + VERIFY( max_blocks_per_chunk == opts.max_blocks_per_chunk ); + VERIFY( largest_required_pool_block == opts.largest_required_pool_block ); +} + +int +main() +{ + test01(); +} diff --git a/libstdc++-v3/testsuite/20_util/unsynchronized_pool_resource/release.cc b/libstdc++-v3/testsuite/20_util/unsynchronized_pool_resource/release.cc new file mode 100644 index 00000000000..e28ec479e7a --- /dev/null +++ b/libstdc++-v3/testsuite/20_util/unsynchronized_pool_resource/release.cc @@ -0,0 +1,113 @@ +// Copyright (C) 2018 Free Software Foundation, Inc. +// +// This file is part of the GNU ISO C++ Library. This library is free +// software; you can redistribute it and/or modify it under the +// terms of the GNU General Public License as published by the +// Free Software Foundation; either version 3, or (at your option) +// any later version. + +// This library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License along +// with this library; see the file COPYING3. If not see +// . + +// { dg-options "-std=gnu++17" } +// { dg-do run { target c++17 } } + +#include +#include +#include + +void +test01() +{ + __gnu_test::memory_resource test_mr; + std::pmr::unsynchronized_pool_resource r(&test_mr); + r.release(); + VERIFY( test_mr.number_of_active_allocations() == 0 ); + r.release(); + VERIFY( test_mr.number_of_active_allocations() == 0 ); + (void) r.allocate(1); + VERIFY( test_mr.number_of_active_allocations() != 0 ); + r.release(); + VERIFY( test_mr.number_of_active_allocations() == 0 ); + r.release(); + VERIFY( test_mr.number_of_active_allocations() == 0 ); +} + +void +test02() +{ + struct nullable_memory_resource : public std::pmr::memory_resource + { + void* + do_allocate(std::size_t bytes, std::size_t alignment) override + { return upstream->allocate(bytes, alignment); } + + void + do_deallocate(void* p, std::size_t bytes, std::size_t alignment) override + { upstream->deallocate(p, bytes, alignment); } + + bool + do_is_equal(const memory_resource& r) const noexcept override + { return &r == this; } + + std::pmr::memory_resource* upstream = std::pmr::get_default_resource(); + }; + + nullable_memory_resource test_mr; + std::pmr::unsynchronized_pool_resource r(&test_mr); + r.release(); + test_mr.upstream = nullptr; + r.release(); // should not need to call anything through upstream pointer +} + +void +test03() +{ + __gnu_test::memory_resource test_mr; + { + std::pmr::unsynchronized_pool_resource r(&test_mr); + // Destructor calls release() + } + VERIFY( test_mr.number_of_active_allocations() == 0 ); + { + std::pmr::unsynchronized_pool_resource r(&test_mr); + (void) r.allocate(1); + VERIFY( test_mr.number_of_active_allocations() != 0 ); + // Destructor calls release() + } + VERIFY( test_mr.number_of_active_allocations() == 0 ); + { + std::pmr::unsynchronized_pool_resource r({10, 16}, &test_mr); + (void) r.allocate(2 * r.options().largest_required_pool_block); + VERIFY( test_mr.number_of_active_allocations() != 0 ); + // Destructor calls release() + } + VERIFY( test_mr.number_of_active_allocations() == 0 ); + { + std::pmr::unsynchronized_pool_resource r({16, 16}, &test_mr); + (void) r.allocate(2); + (void) r.allocate(8); + (void) r.allocate(16); + (void) r.allocate(2); + (void) r.allocate(8); + (void) r.allocate(16); + (void) r.allocate(2 * r.options().largest_required_pool_block); + VERIFY( test_mr.number_of_active_allocations() != 0 ); + // Destructor calls release() + } + VERIFY( test_mr.number_of_active_allocations() == 0 ); +} + +int +main() +{ + test01(); + test02(); + test03(); +}