Program Listing for File DynamicSizePool.hpp¶
↰ Return to documentation for file (umpire/strategy/DynamicSizePool.hpp
)
//////////////////////////////////////////////////////////////////////////////
// Copyright (c) 2016-20, Lawrence Livermore National Security, LLC and Umpire
// project contributors. See the COPYRIGHT file for details.
//
// SPDX-License-Identifier: (MIT)
//////////////////////////////////////////////////////////////////////////////
#ifndef _DYNAMICSIZEPOOL_HPP
#define _DYNAMICSIZEPOOL_HPP
#include <cstddef>
#include <cassert>
#include <string>
#include <iostream>
#include <sstream>
#include <algorithm>
#include "umpire/strategy/StdAllocator.hpp"
#include "umpire/strategy/FixedSizePool.hpp"
#include "umpire/strategy/AllocationStrategy.hpp"
#include "umpire/util/Macros.hpp"
template <class IA = StdAllocator>
class DynamicSizePool
{
protected:
struct Block
{
char *data;
std::size_t size;
std::size_t blockSize;
Block *next;
};
// Allocator for the underlying data
typedef FixedSizePool<struct Block, IA, IA, (1<<6)> BlockPool;
BlockPool blockPool;
// Start of the nodes of used and free block lists
struct Block *usedBlocks;
struct Block *freeBlocks;
// Total blocks in the pool
std::size_t totalBlocks;
// Total size allocated (bytes)
std::size_t totalBytes;
// Allocated size (bytes)
std::size_t allocBytes;
// Minimum size of initial allocation
std::size_t minInitialBytes;
// Minimum size for allocations
std::size_t minBytes;
// High water mark of allocations
std::size_t highWatermark;
// Pointer to our allocator's allocation strategy
umpire::strategy::AllocationStrategy* allocator;
// Search the list of free blocks and return a usable one if that exists, else NULL
void findUsableBlock(struct Block *&best, struct Block *&prev, std::size_t size) {
best = prev = NULL;
for ( struct Block *iter = freeBlocks, *iterPrev = NULL ; iter ; iter = iter->next ) {
if ( iter->size >= size && (!best || iter->size < best->size) ) {
best = iter;
prev = iterPrev;
if ( iter->size == size )
break; // Exact match, won't find a better one, look no further
}
iterPrev = iter;
}
}
inline std::size_t alignmentAdjust(const std::size_t size) {
const std::size_t AlignmentBoundary = 16;
return std::size_t (size + (AlignmentBoundary-1)) & ~(AlignmentBoundary-1);
}
// Allocate a new block and add it to the list of free blocks
void allocateBlock(struct Block *&curr, struct Block *&prev, const std::size_t size) {
std::size_t sizeToAlloc;
if ( freeBlocks == NULL && usedBlocks == NULL )
sizeToAlloc = std::max(size, minInitialBytes);
else
sizeToAlloc = std::max(size, minBytes);
curr = prev = NULL;
void *data = NULL;
// Allocate data
try {
data = allocator->allocate(sizeToAlloc);
}
catch (...) {
UMPIRE_LOG(Error,
"\n\tMemory exhausted at allocation resource. "
"Attempting to give blocks back.\n\t"
<< getCurrentSize() << " Allocated to pool, "
<< getFreeBlocks() << " Free Blocks, "
<< getInUseBlocks() << " Used Blocks\n"
);
freeReleasedBlocks();
UMPIRE_LOG(Error,
"\n\tMemory exhausted at allocation resource. "
"\n\tRetrying allocation operation: "
<< getCurrentSize() << " Bytes still allocated to pool, "
<< getFreeBlocks() << " Free Blocks, "
<< getInUseBlocks() << " Used Blocks\n"
);
try {
data = allocator->allocate(sizeToAlloc);
UMPIRE_LOG(Error,
"\n\tMemory successfully recovered at resource. Allocation succeeded\n"
);
}
catch (...) {
UMPIRE_LOG(Error,
"\n\tUnable to allocate from resource even after giving back free blocks.\n"
"\tThrowing to let application know we have no more memory: "
<< getCurrentSize() << " Bytes still allocated to pool\n"
<< getFreeBlocks() << " Partially Free Blocks, "
<< getInUseBlocks() << " Used Blocks\n"
);
throw;
}
}
totalBlocks += 1;
totalBytes += sizeToAlloc;
// Allocate the block
curr = (struct Block *) blockPool.allocate();
assert("Failed to allocate block for freeBlock List" && curr);
// Find next and prev such that next->data is still smaller than data (keep ordered)
struct Block *next;
for ( next = freeBlocks; next && next->data < data; next = next->next )
prev = next;
// Insert
curr->data = static_cast<char *>(data);
curr->size = sizeToAlloc;
curr->blockSize = sizeToAlloc;
curr->next = next;
// Insert
if (prev) prev->next = curr;
else freeBlocks = curr;
}
void splitBlock(struct Block *&curr, struct Block *&prev, const std::size_t size) {
struct Block *next;
if ( curr->size == size ) {
// Keep it
next = curr->next;
}
else {
// Split the block
std::size_t remaining = curr->size - size;
struct Block *newBlock = (struct Block *) blockPool.allocate();
if (!newBlock) return;
newBlock->data = curr->data + size;
newBlock->size = remaining;
newBlock->blockSize = 0;
newBlock->next = curr->next;
next = newBlock;
curr->size = size;
}
if (prev) prev->next = next;
else freeBlocks = next;
}
void releaseBlock(struct Block *curr, struct Block *prev) {
assert(curr != NULL);
if (prev) prev->next = curr->next;
else usedBlocks = curr->next;
// Find location to put this block in the freeBlocks list
prev = NULL;
for ( struct Block *temp = freeBlocks ; temp && temp->data < curr->data ; temp = temp->next )
prev = temp;
// Keep track of the successor
struct Block *next = prev ? prev->next : freeBlocks;
// Check if prev and curr can be merged
if ( prev && prev->data + prev->size == curr->data && !curr->blockSize ) {
prev->size = prev->size + curr->size;
blockPool.deallocate(curr); // keep data
curr = prev;
}
else if (prev) {
prev->next = curr;
}
else {
freeBlocks = curr;
}
// Check if curr and next can be merged
if ( next && curr->data + curr->size == next->data && !next->blockSize ) {
curr->size = curr->size + next->size;
curr->next = next->next;
blockPool.deallocate(next); // keep data
}
else {
curr->next = next;
}
}
std::size_t freeReleasedBlocks() {
// Release the unused blocks
struct Block *curr = freeBlocks;
struct Block *prev = NULL;
std::size_t freed = 0;
while ( curr ) {
struct Block *next = curr->next;
// The free block list may contain partially released released blocks.
// Make sure to only free blocks that are completely released.
//
if ( curr->size == curr->blockSize ) {
totalBlocks -= 1;
totalBytes -= curr->blockSize;
freed += curr->blockSize;
allocator->deallocate(curr->data);
if ( prev ) prev->next = curr->next;
else freeBlocks = curr->next;
blockPool.deallocate(curr);
}
else {
prev = curr;
}
curr = next;
}
return freed;
}
void coalesceFreeBlocks(std::size_t size) {
UMPIRE_LOG(Debug, "Allocator " << this
<< " coalescing to "
<< size << " bytes from "
<< getFreeBlocks() << " free blocks\n");
freeReleasedBlocks();
void* ptr = allocate(size);
deallocate(ptr);
}
void freeAllBlocks() {
// Release the used blocks
while(usedBlocks) {
releaseBlock(usedBlocks, NULL);
}
freeReleasedBlocks();
assert( "Not all blocks were released properly" && freeBlocks == NULL );
}
public:
DynamicSizePool(
umpire::strategy::AllocationStrategy* strat,
const std::size_t _minInitialBytes = (16 * 1024),
const std::size_t _minBytes = 256
)
: blockPool(),
usedBlocks(NULL),
freeBlocks(NULL),
totalBlocks(0),
totalBytes(0),
allocBytes(0),
minInitialBytes(_minInitialBytes),
minBytes(_minBytes),
highWatermark(0),
allocator(strat) { }
~DynamicSizePool() { freeAllBlocks(); }
void *allocate(std::size_t size) {
struct Block *best, *prev;
size = alignmentAdjust(size);
findUsableBlock(best, prev, size);
// Allocate a block if needed
if (!best) allocateBlock(best, prev, size);
assert(best);
// Split the free block
splitBlock(best, prev, size);
// Push node to the list of used nodes
best->next = usedBlocks;
usedBlocks = best;
// Increment the allocated size
allocBytes += size;
if ( allocBytes > highWatermark )
highWatermark = allocBytes;
// Return the new pointer
return usedBlocks->data;
}
void deallocate(void *ptr) {
assert(ptr);
// Find the associated block
struct Block *curr = usedBlocks, *prev = NULL;
for ( ; curr && curr->data != ptr; curr = curr->next ) {
prev = curr;
}
if (!curr) return;
// Remove from allocBytes
allocBytes -= curr->size;
// Release it
releaseBlock(curr, prev);
}
std::size_t getCurrentSize() const { return allocBytes; }
std::size_t getActualSize() const {
return totalBytes;
}
std::size_t getHighWatermark() const {
return highWatermark;
}
std::size_t getBlocksInPool() const {
return totalBlocks;
}
std::size_t getLargestAvailableBlock() const {
std::size_t largest_block{0};
for (struct Block *temp = freeBlocks; temp; temp = temp->next)
if ( temp->size > largest_block )
largest_block = temp->size;
return largest_block;
}
std::size_t getReleasableSize() const {
std::size_t nblocks = 0;
std::size_t nbytes = 0;
for (struct Block *temp = freeBlocks; temp; temp = temp->next) {
if ( temp->size == temp->blockSize ) {
nbytes += temp->blockSize;
nblocks++;
}
}
return nblocks > 1 ? nbytes : 0;
}
std::size_t getFreeBlocks() const {
std::size_t nb = 0;
for (struct Block *temp = freeBlocks; temp; temp = temp->next)
if ( temp->size == temp->blockSize )
nb++;
return nb;
}
std::size_t getInUseBlocks() const {
std::size_t nb = 0;
for (struct Block *temp = usedBlocks; temp; temp = temp->next) nb++;
return nb;
}
void coalesce() {
if ( getFreeBlocks() > 1 ) {
std::size_t size_to_coalesce = freeReleasedBlocks();
UMPIRE_LOG(Debug, "Attempting to coalesce "
<< size_to_coalesce << " bytes");
coalesceFreeBlocks(size_to_coalesce);
}
}
void release()
{
freeReleasedBlocks();
}
};
#endif