@@ -106,9 +106,66 @@ void pool_memory_resource_impl::initialize_pool(std::size_t initial_size,
106106}
107107
108108pool_memory_resource_impl::block_type pool_memory_resource_impl::expand_pool (
109- std::size_t size, [[maybe_unused]] free_list& blocks, cuda_stream_view stream)
109+ std::size_t size, free_list& blocks, cuda_stream_view stream)
110110{
111- return try_to_expand (size_to_grow (size), size, stream);
111+ auto grow_size = size_to_grow (size);
112+ // When the pool is capped and cannot grow enough to satisfy `size` in a single new upstream
113+ // block, try to reclaim entirely-free upstream blocks (whose budget prevents growth) back to
114+ // upstream, freeing headroom under `maximum_pool_size_` to grow a sufficiently large block.
115+ if (grow_size < size && maximum_pool_size_.has_value ()) {
116+ reclaim_free_blocks (size, blocks, stream);
117+ grow_size = size_to_grow (size);
118+ }
119+ return try_to_expand (grow_size, size, stream);
120+ }
121+
122+ void pool_memory_resource_impl::reclaim_free_blocks (std::size_t size,
123+ free_list& blocks,
124+ cuda_stream_view stream)
125+ {
126+ // Reclamation only frees headroom when the pool has a capped maximum size.
127+ if (!maximum_pool_size_.has_value ()) { return ; }
128+ auto const max_pool_size = maximum_pool_size_.value ();
129+
130+ // If `size` can never fit under the cap even with every free block reclaimed, there is nothing to
131+ // gain: avoid the synchronize and the destructive reclaim on a request that will fail anyway.
132+ if (size > max_pool_size) { return ; }
133+
134+ auto free_iter = blocks.cbegin ();
135+ auto upstream_iter = upstream_blocks_.cbegin ();
136+ using compare_t = decltype (upstream_blocks_)::key_compare;
137+ auto const compare = compare_t {};
138+
139+ // This merge join requires `blocks` and `upstream_blocks_` to remain sorted by `compare_blocks`.
140+ // coalescing_free_list maintains that order on insertion, and upstream_blocks_ uses the same
141+ // comparator. Erasing matched entries below preserves the ordering of both collections.
142+ while (free_iter != blocks.cend () && upstream_iter != upstream_blocks_.cend ()) {
143+ // `current_pool_size_ <= max_pool_size` is an invariant, so the subtraction cannot underflow.
144+ if (max_pool_size - current_pool_size_ >= size) { return ; }
145+
146+ if (compare (*free_iter, *upstream_iter)) {
147+ ++free_iter;
148+ continue ;
149+ }
150+ if (compare (*upstream_iter, *free_iter)) {
151+ ++upstream_iter;
152+ continue ;
153+ }
154+
155+ auto const candidate = free_iter++;
156+ auto const upstream = upstream_iter++;
157+ if (!candidate->is_head () || upstream->size () != candidate->size ()) { continue ; }
158+
159+ auto const blk = *candidate;
160+ // The free lists were merged onto `stream`, which waits on their recorded events. Enqueueing
161+ // the upstream deallocation on the same stream preserves those dependencies without blocking
162+ // the host.
163+ get_upstream_resource ().deallocate (
164+ stream, blk.pointer (), blk.size (), rmm::CUDA_ALLOCATION_ALIGNMENT );
165+ blocks.erase (candidate);
166+ upstream_blocks_.erase (upstream);
167+ current_pool_size_ -= blk.size ();
168+ }
112169}
113170
114171std::size_t pool_memory_resource_impl::size_to_grow (std::size_t size) const
@@ -152,6 +209,8 @@ pool_memory_resource_impl::block_type pool_memory_resource_impl::free_block(
152209 void * ptr, std::size_t size) noexcept
153210{
154211#ifdef RMM_POOL_TRACK_ALLOCATIONS
212+ // Fetch the metadata recorded for this block's suballocation and validate
213+ // the caller's provided size before returning the block to a free list.
155214 if (ptr == nullptr ) return block_type{};
156215 auto const iter = allocated_blocks_.find (static_cast <char *>(ptr));
157216 RMM_LOGGING_ASSERT (iter != allocated_blocks_.end ());
@@ -162,6 +221,9 @@ pool_memory_resource_impl::block_type pool_memory_resource_impl::free_block(
162221
163222 return block;
164223#else
224+ // Reconstruct the block, trusting the validity of the caller's pointer and
225+ // size. A pointer is a block head if and only if it is the start of an
226+ // upstream allocation.
165227 auto const iter = upstream_blocks_.find (static_cast <char *>(ptr));
166228 return block_type{static_cast <char *>(ptr), size, (iter != upstream_blocks_.end ())};
167229#endif
0 commit comments