Improving DynamicPool Performance with a Coalesce HeuristicΒΆ

As needed, the umpire::strategy::DynamicPool will continue to allocate blocks to satisfy allocation requests that cannot be satisfied by blocks currently in the pool it is managing. Under certain application-specific memory allocation patterns, fragmentation within the blocks or allocations that are for sizes greater than the size of the largest available block can cause the pool to grow too large. For example, a problematic allocation pattern is when an application makes several allocations of incrementing size where each allocation is larger than the previous block size allocated.

The umpire::strategy::DynamicPool::coalesce() method may be used to cause the umpire::strategy::DynamicPool to coalesce the releasable blocks into a single larger block. This is accomplished by: tallying the size of all blocks without allocations against them, releasing those blocks back to the memory resource, and creating a new block of the previously tallied size.

Applications may offer a heuristic function to the umpire::strategy::DynamicPool during instantiation that will return true whenever a pool reaches a specific threshold of releasable bytes (represented by completely free blocks) to the total size of the pool. The DynamicPool will call this heuristic function just before it returns from its umpire::strategy::DynamicPool::deallocate() method and when the function returns true, the DynamicPool will call the umpire::strategy::DynamicPool::coalesce() method.

The default heuristic of 100 will cause the DynamicPool to automatically coalesce when all of the bytes in the pool are releasable and there is more than one block in the pool.

A heuristic of 0 will cause the DynamicPool to never automatically coalesce.

Creation of the heuristic function is accomplished by:

  //

The heuristic function is then provided as a parameter when the object is instantiated:

  auto pooled_allocator = rm.makeAllocator<umpire::strategy::DynamicPool>(
                             "HOST_POOL"
                            , allocator
                            , 1024ul
                            , 1024ul
                            , 16

The complete example is included below:

//////////////////////////////////////////////////////////////////////////////
// Copyright (c) 2016-20, Lawrence Livermore National Security, LLC and Umpire
// project contributors. See the COPYRIGHT file for details.
//
// SPDX-License-Identifier: (MIT)
//////////////////////////////////////////////////////////////////////////////
#include "umpire/strategy/DynamicPool.hpp"
#include "umpire/strategy/DynamicPoolHeuristic.hpp"

#include "umpire/Allocator.hpp"
#include "umpire/ResourceManager.hpp"

#include "umpire/util/Macros.hpp"

#include <iostream>

int main(int, char**) {
  auto& rm = umpire::ResourceManager::getInstance();

  auto allocator = rm.getAllocator("HOST");

  //
  // Create a heuristic function that will return true to the DynamicPool
  // object when the threshold of releasable size to total size is 75%.
  //
  auto heuristic_function = umpire::strategy::heuristic_percent_releasable(75);

  //
  // Create a pool with an initial block size of 1 Kb and 1 Kb block size for
  // all subsequent allocations and with our previously created heuristic
  // function.
  //
  auto pooled_allocator = rm.makeAllocator<umpire::strategy::DynamicPool>(
                             "HOST_POOL"
                            , allocator
                            , 1024ul
                            , 1024ul
                            , 16
                            , heuristic_function);

  //
  // Obtain a pointer to our specifi DynamicPool instance in order to see the
  // DynamicPool-specific statistics
  //
  auto dynamic_pool = umpire::util::unwrap_allocator<umpire::strategy::DynamicPool>(pooled_allocator);

  void* a[4];
  for (int i = 0; i < 4; ++i)
    a[i] = pooled_allocator.allocate(1024);

  for (int i = 0; i < 4; ++i) {
    pooled_allocator.deallocate(a[i]);
    std::cout
      << "Pool has " << pooled_allocator.getActualSize() << " bytes of memory. "
      << pooled_allocator.getCurrentSize() << " bytes are used. "
      << dynamic_pool->getBlocksInPool() << " blocks are in the pool. "
      << dynamic_pool->getReleasableSize() << " bytes are releaseable. "
      << std::endl;
  }

  return 0;
}