cachelib/allocator/memory/MemoryPoolManager.cpp (259 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/MemoryPoolManager.h" #pragma GCC diagnostic push #pragma GCC diagnostic ignored "-Wconversion" #include <folly/Format.h> #pragma GCC diagnostic pop using namespace facebook::cachelib; constexpr unsigned int MemoryPoolManager::kMaxPools; MemoryPoolManager::MemoryPoolManager(SlabAllocator& slabAlloc) : slabAlloc_(slabAlloc) {} MemoryPoolManager::MemoryPoolManager( const serialization::MemoryPoolManagerObject& object, SlabAllocator& slabAlloc) : nextPoolId_(*object.nextPoolId_ref()), slabAlloc_(slabAlloc) { if (!slabAlloc_.isRestorable()) { throw std::logic_error( "Memory Pool Manager can not be restored," " slabAlloc not restored"); } // Check if nextPoolid is restored properly or not. If not restored, // throw error if (!object.nextPoolId_ref().is_set()) { throw std::logic_error( "Memory Pool Manager can not be restored," " nextPoolId is not set"); } // Number of items in pools must be same as nextPoolId, if not throw error if (object.pools_ref()->size() != static_cast<size_t>(nextPoolId_)) { throw std::logic_error( "Memory Pool Manager can not be restored," "pools size is not equal to nextPoolId"); } size_t slabsAdvised = 0; for (size_t i = 0; i < object.pools_ref()->size(); ++i) { pools_[i].reset(new MemoryPool(object.pools_ref()[i], slabAlloc_)); slabsAdvised += pools_[i]->getNumSlabsAdvised(); } for (const auto& kv : *object.poolsByName_ref()) { poolsByName_.insert(kv); } // Number items in the poolsByName map must be same as nextPoolId, if not // throw error if (object.poolsByName_ref()->size() != static_cast<size_t>(nextPoolId_)) { throw std::logic_error( "Memory Pool Manager can not be restored," "poolsByName size is not equal to nextPoolId"); } numSlabsToAdvise_ = slabAlloc_.numSlabsReclaimable(); if (slabsAdvised != numSlabsToAdvise_) { throw std::logic_error(folly::sformat( "Aggregate of advised slabs in pools {} is not same as SlabAllocator" " number of slabs advised {}", slabsAdvised, numSlabsToAdvise_.load())); } } size_t MemoryPoolManager::getRemainingSizeLocked() const noexcept { const size_t totalSize = slabAlloc_.getNumUsableAndAdvisedSlabs() * Slab::kSize; // check if there is enough space left. size_t sum = 0; for (PoolId id = 0; id < nextPoolId_; id++) { sum += pools_[id]->getPoolSize(); } XDCHECK_LE(sum, totalSize); return totalSize - sum; } PoolId MemoryPoolManager::createNewPool(folly::StringPiece name, size_t poolSize, const std::set<uint32_t>& allocSizes) { folly::SharedMutex::WriteHolder l(lock_); if (poolsByName_.find(name) != poolsByName_.end()) { throw std::invalid_argument("Duplicate pool"); } if (nextPoolId_ == kMaxPools) { throw std::logic_error("All pools exhausted"); } const size_t remaining = getRemainingSizeLocked(); if (remaining < poolSize) { // not enough memory to create a new pool. throw std::invalid_argument(folly::sformat( "Not enough memory ({} bytes) to create a new pool of size {} bytes", remaining, poolSize)); } const PoolId id = nextPoolId_; pools_[id].reset(new MemoryPool(id, poolSize, slabAlloc_, allocSizes)); poolsByName_.insert({name.str(), id}); nextPoolId_++; return id; } MemoryPool& MemoryPoolManager::getPoolByName(const std::string& name) const { folly::SharedMutex::ReadHolder l(lock_); auto it = poolsByName_.find(name); if (it == poolsByName_.end()) { throw std::invalid_argument(folly::sformat("Invalid pool name {}", name)); } auto poolId = it->second; XDCHECK_LT(poolId, nextPoolId_.load()); XDCHECK_GE(poolId, 0); XDCHECK(pools_[poolId] != nullptr); return *pools_[poolId]; } MemoryPool& MemoryPoolManager::getPoolById(PoolId id) const { // does not need to grab the lock since nextPoolId_ is atomic and we always // bump it up after setting everything up.. if (id < nextPoolId_ && id >= 0) { XDCHECK(pools_[id] != nullptr); return *pools_[id]; } throw std::invalid_argument(folly::sformat("Invalid pool id {}", id)); } const std::string& MemoryPoolManager::getPoolNameById(PoolId id) const { folly::SharedMutex::ReadHolder l(lock_); for (const auto& pair : poolsByName_) { if (pair.second == id) { return pair.first; } } throw std::invalid_argument(folly::sformat("Invali pool id {}", id)); } serialization::MemoryPoolManagerObject MemoryPoolManager::saveState() const { if (!slabAlloc_.isRestorable()) { throw std::logic_error("Memory Pool Manager can not be restored"); } serialization::MemoryPoolManagerObject object; object.pools_ref().emplace(); for (PoolId i = 0; i < nextPoolId_; ++i) { object.pools_ref()->push_back(pools_[i]->saveState()); } object.poolsByName_ref().emplace(); for (const auto& kv : poolsByName_) { object.poolsByName_ref()->insert(kv); } object.nextPoolId_ref() = nextPoolId_; return object; } std::set<PoolId> MemoryPoolManager::getPoolIds() const { std::set<PoolId> ret; for (PoolId id = 0; id < nextPoolId_; ++id) { ret.insert(id); } return ret; } bool MemoryPoolManager::resizePools(PoolId src, PoolId dest, size_t bytes) { auto& srcPool = getPoolById(src); auto& destPool = getPoolById(dest); folly::SharedMutex::WriteHolder l(lock_); if (srcPool.getPoolSize() < bytes) { return false; } // move the memory. srcPool.resize(srcPool.getPoolSize() - bytes); destPool.resize(destPool.getPoolSize() + bytes); return true; } bool MemoryPoolManager::shrinkPool(PoolId pid, size_t bytes) { auto& pool = getPoolById(pid); folly::SharedMutex::WriteHolder l(lock_); if (pool.getPoolSize() < bytes) { return false; } pool.resize(pool.getPoolSize() - bytes); return true; } bool MemoryPoolManager::growPool(PoolId pid, size_t bytes) { auto& pool = getPoolById(pid); folly::SharedMutex::WriteHolder l(lock_); const auto remaining = getRemainingSizeLocked(); if (remaining < bytes) { return false; } pool.resize(pool.getPoolSize() + bytes); return true; } std::set<PoolId> MemoryPoolManager::getPoolsOverLimit() const { std::set<PoolId> res; folly::SharedMutex::ReadHolder l(lock_); for (const auto& kv : poolsByName_) { const auto poolId = kv.second; const auto& pool = getPoolById(poolId); if (pool.overLimit()) { res.insert(poolId); } } return res; } // Helper routine to determine the target number of slabs to be advised in // each pool. This first assigns the target based on integer division and // any remainder is addressed afterwards. The goal is to make the sum of // target slabs to advise equal to numSlabsToAdvise_ std::unordered_map<PoolId, uint64_t> MemoryPoolManager::getTargetSlabsToAdvise( std::set<PoolId> poolIds, uint64_t totalSlabsInUse, std::unordered_map<PoolId, size_t>& numSlabsInUse) const { uint64_t sum = 0; std::unordered_map<PoolId, uint64_t> targets; for (auto id : poolIds) { targets[id] = numSlabsInUse[id] * numSlabsToAdvise_ / totalSlabsInUse; sum += targets[id]; } for (auto it = poolIds.begin(); it != poolIds.end() && sum < numSlabsToAdvise_; ++it) { auto id = *it; if (((targets[id] * totalSlabsInUse) / numSlabsToAdvise_) < numSlabsInUse[id]) { targets[id]++; sum++; } } XDCHECK_EQ(sum, numSlabsToAdvise_); return targets; } PoolAdviseReclaimData MemoryPoolManager::calcNumSlabsToAdviseReclaim( const std::set<PoolId>& poolIds) const { folly::SharedMutex::WriteHolder l(lock_); uint64_t totalSlabsAdvised = 0; uint64_t totalSlabsInUse = 0; std::unordered_map<PoolId, size_t> numSlabsInUse; for (auto id : poolIds) { // Get the individual pool slab usage and cache it so that any changes to // it do not reflect in the subsequent calculations. numSlabsInUse[id] = pools_[id]->getCurrentUsedSize() / Slab::kSize; totalSlabsInUse += numSlabsInUse[id]; totalSlabsAdvised += pools_[id]->getNumSlabsAdvised(); } PoolAdviseReclaimData results; results.advise = false; // No slabs in use or no slabs advised and no slabs to advise return empty map if (totalSlabsInUse == 0 || (numSlabsToAdvise_ == 0 && totalSlabsAdvised == 0)) { return results; } auto poolAdviseTargets = getTargetSlabsToAdvise(poolIds, totalSlabsInUse, numSlabsInUse); if (numSlabsToAdvise_ == totalSlabsAdvised) { // No need to advise-away or reclaim any new slabs. // Just rebalance the advised away slabs in each pool for (auto& target : poolAdviseTargets) { pools_[target.first]->setNumSlabsAdvised(target.second); } return results; } if (numSlabsToAdvise_ > totalSlabsAdvised) { results.advise = true; uint64_t diff = numSlabsToAdvise_ - totalSlabsAdvised; for (auto id : poolIds) { if (diff == 0) { break; } uint64_t slabsToAdvise = poolAdviseTargets[id]; uint64_t currSlabsAdvised = pools_[id]->getNumSlabsAdvised(); if (slabsToAdvise <= currSlabsAdvised) { continue; } uint64_t poolSlabsToAdvise = std::min(slabsToAdvise - currSlabsAdvised, diff); results.poolAdviseReclaimMap.emplace(id, poolSlabsToAdvise); diff -= poolSlabsToAdvise; } } else { uint64_t diff = totalSlabsAdvised - numSlabsToAdvise_; for (auto id : poolIds) { if (diff == 0) { break; } auto slabsToAdvise = poolAdviseTargets[id]; auto currSlabsAdvised = pools_[id]->getNumSlabsAdvised(); if (slabsToAdvise >= currSlabsAdvised) { continue; } uint64_t poolSlabsToReclaim = std::min(currSlabsAdvised - slabsToAdvise, diff); results.poolAdviseReclaimMap.emplace(id, poolSlabsToReclaim); diff -= poolSlabsToReclaim; } } return results; }