// ----------------------------------------------------------------------
// File: RingAllocator.hh
// Author: Georgios Bitzes - CERN
// ----------------------------------------------------------------------
/************************************************************************
* quarkdb - a redis-like highly available key-value store *
* Copyright (C) 2016 CERN/Switzerland *
* *
* This program 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 of the License, or *
* (at your option) any later version. *
* *
* This program 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 program. If not, see .*
************************************************************************/
#ifndef QUARKDB_RING_ALLOCATOR_HH
#define QUARKDB_RING_ALLOCATOR_HH
#include "PinnedBuffer.hh"
#include
#include
#include
#include
namespace quarkdb {
//------------------------------------------------------------------------------
// A fancy way of saying "non-copyable contiguous array".
//------------------------------------------------------------------------------
class MemoryRegion : public std::enable_shared_from_this {
public:
//----------------------------------------------------------------------------
// Construct
//----------------------------------------------------------------------------
static std::shared_ptr Construct(size_t n) {
return std::make_shared(n);
}
//----------------------------------------------------------------------------
// Private constructor - use "Construct" method which gives you a shared_ptr.
// Don't use this! Should have been private, but make_shared doesn't seem
// to work with private constructors.
//----------------------------------------------------------------------------
MemoryRegion(size_t n) {
allocated = 0u;
region.resize(n);
}
//----------------------------------------------------------------------------
// NO moving: All referencing PinnedBuffers to the moved-from object would
// become invalid..
//----------------------------------------------------------------------------
MemoryRegion& operator=(MemoryRegion&& other) = delete;
MemoryRegion(MemoryRegion&& other) = delete;
//----------------------------------------------------------------------------
// No copy-constructor, no copy-assignment for obvious reasons
//----------------------------------------------------------------------------
MemoryRegion& operator=(const MemoryRegion& other) = delete;
MemoryRegion(const MemoryRegion& other) = delete;
//----------------------------------------------------------------------------
// Allocate this amount of bytes, filling a PinnedBuffer.
//
// Return an empty optional if we don't have enough space to service
// this request.
//----------------------------------------------------------------------------
std::optional allocate(size_t bytes) {
if(allocated + bytes > region.size()) {
return {};
}
char* ptr = (char*) (region.data() + allocated);
allocated += bytes;
return PinnedBuffer(shared_from_this(), ptr, bytes);
}
//----------------------------------------------------------------------------
// Reset all allocations, and start from the beginning. Only call this
// if you are certain there are no other references to this memory block.
//----------------------------------------------------------------------------
void resetAllocations() {
allocated = 0u;
}
//----------------------------------------------------------------------------
// Return pointer to start of the array
//----------------------------------------------------------------------------
char* data() {
return (char*) region.data();
}
//----------------------------------------------------------------------------
// Return const pointer to start of the array
//----------------------------------------------------------------------------
const char* data() const {
return (const char*) region.data();
}
//----------------------------------------------------------------------------
// Return size
//----------------------------------------------------------------------------
size_t size() const {
return region.size();
}
//----------------------------------------------------------------------------
// Return bytes consumed so far
//----------------------------------------------------------------------------
size_t bytesConsumed() const {
return allocated;
}
//----------------------------------------------------------------------------
// Return amount of free bytes
//----------------------------------------------------------------------------
size_t bytesFree() const {
return size() - bytesConsumed();
}
//----------------------------------------------------------------------------
// Return number of references to this object
//----------------------------------------------------------------------------
long refcount() const {
return weak_from_this().use_count();
}
private:
std::vector region;
size_t allocated = 0u;
};
//------------------------------------------------------------------------------
// There are certain memory allocation patterns which follow a queue-like
// behaviour: Requests on a single connection being a major example:
//
// - REQ1
// - REQ2
// - REQ3
// - REQ4
//
// Allocation pattern: REQ1, REQ2, REQ3, REQ4, ...
// De-allocation pattern: REQ1, REQ2, REQ3, REQ4, ...
//
// In such case, major performance gains can be realized by recycling
// the memory per-connection, and relieving a lot of pressure from the global
// memory allocator.
//
// Sized correctly, such an allocator can absorb virtually all hits to malloc
// that a single connection servicing requests would make. Moreover, each
// allocation request serviced by us will be far cheaper than malloc, as we're
// simply adjusting pointers, and not doing fully-general memory accounting.
//
// The cherry-on-top is that we benefit from cache locality, since requests
// will generally be accessed in the same order they are allocated.
//
// Do not use this unless the memory regions requested are (roughly)
// de-allocated in the same order they were allocated, as memory consumption
// will explode otherwise. It should, however, still behave correctly even
// in such case.
//------------------------------------------------------------------------------
// class RingAllocator {
// public:
// //----------------------------------------------------------------------------
// // Provide the size of allocated memory blocks, and the number of blocks to
// // keep around for recycling.
// //
// // If a request for memory exceeds this size, direct malloc is used.
// //
// // TODO
// //----------------------------------------------------------------------------
// RingAllocator(size_t blockSize, size_t minBlockCount, size_t maxBlockCount) :
// blockSize(blockSize), minBlockCount(minBlockCount),
// maxBlockCount(maxBlockCount) {}
// private:
// size_t blockSize;
// size_t minBlockCount;
// size_t maxBlockCount;
// };
}
#endif