cachelib/allocator/HitsPerSlabStrategy.cpp (130 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/HitsPerSlabStrategy.h" #include <folly/logging/xlog.h> #include <algorithm> #include <functional> #include "cachelib/allocator/Util.h" namespace facebook { namespace cachelib { HitsPerSlabStrategy::HitsPerSlabStrategy(Config config) : RebalanceStrategy(HitsPerSlab), config_(std::move(config)) {} // The list of allocation classes to be rebalanced is determined by: // // 0. Filter out classes that have below minSlabThreshold_ // // 1. Filter out classes that have just gained a slab recently // // 2. pick victim from the one that has poorest hitsPerSlab ClassId HitsPerSlabStrategy::pickVictim(const Config& config, const CacheBase& cache, PoolId pid, const PoolStats& stats) { auto victims = stats.getClassIds(); // ignore allocation classes that have fewer than the threshold of slabs. victims = filterByNumEvictableSlabs(stats, std::move(victims), config.minSlabs); // ignore allocation classes that recently gained a slab. These will be // growing in their eviction age and we want to let the evicitons stabilize // before we consider them again. victims = filterVictimsByHoldOff(pid, stats, std::move(victims)); // filter out alloc classes with less than the minimum tail age if (config.minLruTailAge != 0) { // we are only concerned about the eviction age and not the projected age. const auto poolEvictionAgeStats = cache.getPoolEvictionAgeStats(pid, /* projectionLength */ 0); victims = filterByMinTailAge(poolEvictionAgeStats, std::move(victims), config.minLruTailAge); } if (victims.empty()) { return Slab::kInvalidClassId; } const auto& poolState = getPoolState(pid); auto victimClassId = pickVictimByFreeMem( victims, stats, config.getFreeMemThreshold(), poolState); if (victimClassId != Slab::kInvalidClassId) { return victimClassId; } return *std::min_element( victims.begin(), victims.end(), [&](ClassId a, ClassId b) { double weight_a = config.getWeight ? config.getWeight(pid, a, stats) : 1; double weight_b = config.getWeight ? config.getWeight(pid, b, stats) : 1; return poolState.at(a).projectedDeltaHitsPerSlab(stats) * weight_a < poolState.at(b).projectedDeltaHitsPerSlab(stats) * weight_b; }); } // The list of allocation classes to be receiver is determined by: // // 0. Filter out classes that have no evictions // // 1. Filter out classes that have no slabs // // 2. pick receiver from the one that has highest hitsPerSlab ClassId HitsPerSlabStrategy::pickReceiver(const Config& config, PoolId pid, const PoolStats& stats, ClassId victim) const { auto receivers = stats.getClassIds(); receivers.erase(victim); const auto& poolState = getPoolState(pid); // filter out alloc classes that are not evicting receivers = filterByNoEvictions(stats, std::move(receivers), poolState); // filter out receivers who currently dont have any slabs. Their delta hits // do not make much sense. receivers = filterByNumEvictableSlabs(stats, std::move(receivers), 0); if (receivers.empty()) { return Slab::kInvalidClassId; } return *std::max_element( receivers.begin(), receivers.end(), [&](ClassId a, ClassId b) { double weight_a = config.getWeight ? config.getWeight(pid, a, stats) : 1; double weight_b = config.getWeight ? config.getWeight(pid, b, stats) : 1; return poolState.at(a).deltaHitsPerSlab(stats) * weight_a < poolState.at(b).deltaHitsPerSlab(stats) * weight_b; }); } RebalanceContext HitsPerSlabStrategy::pickVictimAndReceiverImpl( const CacheBase& cache, PoolId pid) { if (!cache.getPool(pid).allSlabsAllocated()) { XLOGF(DBG, "Pool Id: {}" " does not have all its slabs allocated" " and does not need rebalancing.", static_cast<int>(pid)); return kNoOpContext; } const auto poolStats = cache.getPoolStats(pid); const auto config = getConfigCopy(); RebalanceContext ctx; ctx.victimClassId = pickVictim(config, cache, pid, poolStats); ctx.receiverClassId = pickReceiver(config, pid, poolStats, ctx.victimClassId); if (ctx.victimClassId == ctx.receiverClassId || ctx.victimClassId == Slab::kInvalidClassId || ctx.receiverClassId == Slab::kInvalidClassId) { return kNoOpContext; } auto& poolState = getPoolState(pid); double weightVictim = 1; double weightReceiver = 1; if (config.getWeight) { weightReceiver = config.getWeight(pid, ctx.receiverClassId, poolStats); weightVictim = config.getWeight(pid, ctx.victimClassId, poolStats); } const auto victimProjectedDeltaHitsPerSlab = poolState.at(ctx.victimClassId).projectedDeltaHitsPerSlab(poolStats) * weightVictim; const auto receiverDeltaHitsPerSlab = poolState.at(ctx.receiverClassId).deltaHitsPerSlab(poolStats) * weightReceiver; XLOGF(DBG, "Rebalancing: receiver = {}, receiver delta hits per slab = {}, victim " "= {}, victim projected delta hits per slab = {}", static_cast<int>(ctx.receiverClassId), receiverDeltaHitsPerSlab, static_cast<int>(ctx.victimClassId), victimProjectedDeltaHitsPerSlab); const auto improvement = receiverDeltaHitsPerSlab - victimProjectedDeltaHitsPerSlab; if (receiverDeltaHitsPerSlab < victimProjectedDeltaHitsPerSlab || improvement < config.minDiff || improvement < config.diffRatio * static_cast<long double>( victimProjectedDeltaHitsPerSlab)) { XLOG(DBG, " Not enough to trigger slab rebalancing"); return kNoOpContext; } // start a hold off so that the receiver does not become a victim soon // enough. poolState.at(ctx.receiverClassId).startHoldOff(); // update all alloc classes' hits state to current hits so that next time we // only look at the delta hits sicne the last rebalance. for (const auto i : poolStats.getClassIds()) { poolState[i].updateHits(poolStats); } return ctx; } ClassId HitsPerSlabStrategy::pickVictimImpl(const CacheBase& cache, PoolId pid) { const auto poolStats = cache.getPoolStats(pid); const auto config = getConfigCopy(); auto victimClassId = pickVictim(config, cache, pid, poolStats); auto& poolState = getPoolState(pid); // update all alloc classes' hits state to current hits so that next time we // only look at the delta hits sicne the last resize. for (const auto i : poolStats.getClassIds()) { poolState[i].updateHits(poolStats); } return victimClassId; } } // namespace cachelib } // namespace facebook