cachelib/allocator/memory/MemoryAllocator.cpp (208 lines of code) (raw):
/*
* Copyright (c) Facebook, Inc. and its affiliates.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include "cachelib/allocator/memory/MemoryAllocator.h"
#include <folly/Format.h>
using namespace facebook::cachelib;
namespace {
// helper function.
void checkConfig(const MemoryAllocator::Config& config) {
if (config.allocSizes.size() > MemoryAllocator::kMaxClasses) {
throw std::invalid_argument("Too many allocation classes");
}
}
} // namespace
MemoryAllocator::MemoryAllocator(Config config,
void* memoryStart,
size_t memSize)
: config_(std::move(config)),
slabAllocator_(memoryStart,
memSize,
{config_.disableFullCoredump, config_.lockMemory}),
memoryPoolManager_(slabAllocator_) {
checkConfig(config_);
}
MemoryAllocator::MemoryAllocator(Config config, size_t memSize)
: config_(std::move(config)),
slabAllocator_(memSize,
{config_.disableFullCoredump, config_.lockMemory}),
memoryPoolManager_(slabAllocator_) {
checkConfig(config_);
}
MemoryAllocator::MemoryAllocator(
const serialization::MemoryAllocatorObject& object,
void* memoryStart,
size_t memSize,
bool disableCoredump)
: config_(std::set<uint32_t>{object.allocSizes_ref()->begin(),
object.allocSizes_ref()->end()},
*object.enableZeroedSlabAllocs_ref(),
disableCoredump,
*object.lockMemory_ref()),
slabAllocator_(*object.slabAllocator_ref(),
memoryStart,
memSize,
{config_.disableFullCoredump, config_.lockMemory}),
memoryPoolManager_(*object.memoryPoolManager_ref(), slabAllocator_) {
checkConfig(config_);
}
void* MemoryAllocator::allocate(PoolId id, uint32_t size) {
auto& mp = memoryPoolManager_.getPoolById(id);
return mp.allocate(size);
}
void* MemoryAllocator::allocateZeroedSlab(PoolId id) {
if (!config_.enableZeroedSlabAllocs) {
throw std::logic_error("Zeroed Slab allcoation is not enabled");
}
auto& mp = memoryPoolManager_.getPoolById(id);
return mp.allocateZeroedSlab();
}
PoolId MemoryAllocator::addPool(folly::StringPiece name,
size_t size,
const std::set<uint32_t>& allocSizes,
bool ensureProvisionable) {
const std::set<uint32_t>& poolAllocSizes =
allocSizes.empty() ? config_.allocSizes : allocSizes;
if (poolAllocSizes.size() > MemoryAllocator::kMaxClasses) {
throw std::invalid_argument("Too many allocation classes");
}
if (ensureProvisionable &&
cachelib::Slab::kSize * poolAllocSizes.size() > size) {
throw std::invalid_argument(folly::sformat(
"Pool {} cannot have at least one slab for each allocation class. "
"{} bytes required, {} bytes given.",
name,
cachelib::Slab::kSize * poolAllocSizes.size(),
size));
}
return memoryPoolManager_.createNewPool(name, size, poolAllocSizes);
}
PoolId MemoryAllocator::getPoolId(const std::string& name) const noexcept {
try {
const auto& mp = memoryPoolManager_.getPoolByName(name);
return mp.getId();
} catch (const std::invalid_argument&) {
return Slab::kInvalidPoolId;
}
}
void MemoryAllocator::free(void* memory) {
auto& mp = getMemoryPool(memory);
mp.free(memory);
}
MemoryPool& MemoryAllocator::getMemoryPool(const void* memory) const {
const auto* header = slabAllocator_.getSlabHeader(memory);
if (header == nullptr) {
throw std::invalid_argument("not recognized by this allocator");
}
auto poolId = header->poolId;
return memoryPoolManager_.getPoolById(poolId);
}
ClassId MemoryAllocator::getAllocationClassId(PoolId poolId,
uint32_t size) const {
const auto& pool = memoryPoolManager_.getPoolById(poolId);
return pool.getAllocationClassId(size);
}
serialization::MemoryAllocatorObject MemoryAllocator::saveState() {
serialization::MemoryAllocatorObject object;
object.allocSizes_ref()->insert(config_.allocSizes.begin(),
config_.allocSizes.end());
*object.enableZeroedSlabAllocs_ref() = config_.enableZeroedSlabAllocs;
*object.lockMemory_ref() = config_.lockMemory;
*object.slabAllocator_ref() = slabAllocator_.saveState();
*object.memoryPoolManager_ref() = memoryPoolManager_.saveState();
return object;
}
SlabReleaseContext MemoryAllocator::startSlabRelease(
PoolId pid,
ClassId victim,
ClassId receiver,
SlabReleaseMode mode,
const void* hint,
SlabReleaseAbortFn shouldAbortFn) {
auto& pool = memoryPoolManager_.getPoolById(pid);
return pool.startSlabRelease(victim, receiver, mode, hint,
config_.enableZeroedSlabAllocs, shouldAbortFn);
}
bool MemoryAllocator::isAllocFreed(const SlabReleaseContext& ctx,
void* memory) const {
const auto& pool = memoryPoolManager_.getPoolById(ctx.getPoolId());
const auto& ac = pool.getAllocationClass(ctx.getClassId());
return ac.isAllocFreed(ctx, memory);
}
bool MemoryAllocator::allAllocsFreed(const SlabReleaseContext& ctx) const {
const auto& pool = memoryPoolManager_.getPoolById(ctx.getPoolId());
const auto& ac = pool.getAllocationClass(ctx.getClassId());
return ac.allFreed(ctx.getSlab());
}
void MemoryAllocator::processAllocForRelease(
const SlabReleaseContext& ctx,
void* memory,
const std::function<void(void*)>& callback) const {
const auto& pool = memoryPoolManager_.getPoolById(ctx.getPoolId());
const auto& ac = pool.getAllocationClass(ctx.getClassId());
return ac.processAllocForRelease(ctx, memory, callback);
}
void MemoryAllocator::completeSlabRelease(const SlabReleaseContext& context) {
auto pid = context.getPoolId();
auto& pool = memoryPoolManager_.getPoolById(pid);
pool.completeSlabRelease(context);
}
void MemoryAllocator::abortSlabRelease(const SlabReleaseContext& context) {
auto pid = context.getPoolId();
auto& pool = memoryPoolManager_.getPoolById(pid);
pool.abortSlabRelease(context);
}
std::set<uint32_t> MemoryAllocator::generateAllocSizes(
double factor,
uint32_t maxSize,
uint32_t minSize,
bool reduceFragmentation) {
if (maxSize > Slab::kSize) {
throw std::invalid_argument(
folly::sformat("maximum alloc size {} is more than the slab size {}",
maxSize, Slab::kSize));
}
if (factor <= 1.0) {
throw std::invalid_argument(folly::sformat("invalid factor {}", factor));
}
// Returns the next chunk size. Uses the previous size and factor to select a
// size that increases the number of chunks per slab by at least one to reduce
// slab wastage. Also increases the chunk size to the maximum that maintains
// the same number of chunks per slab (for example: if slabs are 1 MB then a
// chunk size of 300 KB can be upgraded to 333 KB while maintaining 3 chunks
// per 1 MB slab).
auto nextSize = [=](uint32_t prevSize, double incFactor) {
// Increment by incFactor until we have a new number of chunks per slab.
uint32_t newSize = prevSize;
do {
auto tmpPrevSize = newSize;
newSize = util::getAlignedSize(static_cast<uint32_t>(newSize * incFactor),
kAlignment);
if (newSize == tmpPrevSize) {
throw std::invalid_argument(
folly::sformat("invalid incFactor {}", incFactor));
}
if (newSize > Slab::kSize) {
return newSize;
}
} while (Slab::kSize / newSize == Slab::kSize / prevSize);
// Now make sure we're selecting the maximum chunk size while maintaining
// the number of chunks per slab.
const uint32_t perSlab = static_cast<uint32_t>(Slab::kSize) / newSize;
XDCHECK_GT(perSlab, 0ULL);
const uint32_t maxChunkSize = static_cast<uint32_t>(Slab::kSize) / perSlab;
// Align down to maintain perslab
newSize = maxChunkSize - maxChunkSize % kAlignment;
XDCHECK_EQ(newSize % kAlignment, 0ULL);
XDCHECK_EQ(static_cast<uint32_t>(Slab::kSize) / newSize, perSlab);
return newSize;
};
std::set<uint32_t> allocSizes;
auto size = minSize;
while (size < maxSize) {
const auto nPerSlab = Slab::kSize / size;
// if we can not make more than one alloc per slab, we just default to the
// max alloc size.
if (nPerSlab <= 1) {
break;
}
allocSizes.insert(size);
if (reduceFragmentation) {
size = nextSize(size, factor);
} else {
auto prevSize = size;
size = util::getAlignedSize(static_cast<uint32_t>(size * factor),
kAlignment);
if (prevSize == size) {
throw std::invalid_argument(
folly::sformat("invalid incFactor {}", factor));
}
}
}
allocSizes.insert(util::getAlignedSize(maxSize, kAlignment));
return allocSizes;
}