Skip to content

Commit d5fc2b4

Browse files
committed
Multithreaded evictor
1 parent b809202 commit d5fc2b4

File tree

13 files changed

+181
-75
lines changed

13 files changed

+181
-75
lines changed

cachelib/allocator/BackgroundEvictor-inl.h

Lines changed: 49 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -22,11 +22,10 @@ namespace cachelib {
2222

2323
template <typename CacheT>
2424
BackgroundEvictor<CacheT>::BackgroundEvictor(Cache& cache,
25-
std::shared_ptr<BackgroundEvictorStrategy> strategy,
26-
unsigned int tid)
25+
std::shared_ptr<BackgroundEvictorStrategy> strategy)
2726
: cache_(cache),
28-
strategy_(strategy),
29-
tid_(tid) {
27+
strategy_(strategy)
28+
{
3029
}
3130

3231
template <typename CacheT>
@@ -35,47 +34,64 @@ BackgroundEvictor<CacheT>::~BackgroundEvictor() { stop(std::chrono::seconds(0));
3534
template <typename CacheT>
3635
void BackgroundEvictor<CacheT>::work() {
3736
try {
38-
for (const auto pid : cache_.getRegularPoolIds()) {
39-
checkAndRun(pid);
40-
}
37+
checkAndRun();
4138
} catch (const std::exception& ex) {
4239
XLOGF(ERR, "BackgroundEvictor interrupted due to exception: {}", ex.what());
4340
}
4441
}
4542

43+
template <typename CacheT>
44+
void BackgroundEvictor<CacheT>::setAssignedMemory(std::vector<std::tuple<TierId, PoolId, ClassId>> &&assignedMemory)
45+
{
46+
XLOG(INFO, "Memory assigned to background worker:");
47+
for (auto [tid, pid, cid] : assignedMemory) {
48+
XLOGF(INFO, "Tid: {}, Pid: {}, Cid: {}", tid, pid, cid);
49+
}
50+
51+
mutex.lock_combine([this, &assignedMemory]{
52+
this->assignedMemory_ = std::move(assignedMemory);
53+
});
54+
}
55+
4656
// Look for classes that exceed the target memory capacity
4757
// and return those for eviction
4858
template <typename CacheT>
49-
void BackgroundEvictor<CacheT>::checkAndRun(PoolId pid) {
50-
const auto& mpStats = cache_.getPoolByTid(pid,tid_).getStats();
59+
void BackgroundEvictor<CacheT>::checkAndRun() {
60+
auto assignedMemory = mutex.lock_combine([this]{
61+
return assignedMemory_;
62+
});
63+
5164
unsigned int evictions = 0;
52-
unsigned int classes = 0;
53-
for (auto& cid : mpStats.classIds) {
54-
classes++;
55-
auto batch = strategy_->calculateBatchSize(cache_,tid_,pid,cid);
56-
if (!batch) {
57-
continue;
58-
}
59-
60-
stats.evictionSize.add(batch * mpStats.acStats.at(cid).allocSize);
61-
62-
//try evicting BATCH items from the class in order to reach free target
63-
auto evicted =
64-
BackgroundEvictorAPIWrapper<CacheT>::traverseAndEvictItems(cache_,
65-
tid_,pid,cid,batch);
66-
evictions += evicted;
67-
68-
const size_t cid_id = (size_t)mpStats.acStats.at(cid).allocSize;
69-
auto it = evictions_per_class_.find(cid_id);
70-
if (it != evictions_per_class_.end()) {
71-
it->second += evicted;
72-
} else {
73-
evictions_per_class_[cid_id] = 0;
74-
}
65+
std::set<ClassId> classes{};
66+
67+
for (const auto [tid, pid, cid] : assignedMemory) {
68+
classes.insert(cid);
69+
const auto& mpStats = cache_.getPoolByTid(pid,tid).getStats();
70+
auto batch = strategy_->calculateBatchSize(cache_,tid,pid,cid);
71+
if (!batch) {
72+
continue;
73+
}
74+
75+
stats.evictionSize.add(batch * mpStats.acStats.at(cid).allocSize);
76+
77+
//try evicting BATCH items from the class in order to reach free target
78+
auto evicted =
79+
BackgroundEvictorAPIWrapper<CacheT>::traverseAndEvictItems(cache_,
80+
tid,pid,cid,batch);
81+
evictions += evicted;
82+
83+
const size_t cid_id = (size_t)mpStats.acStats.at(cid).allocSize;
84+
auto it = evictions_per_class_.find(cid_id);
85+
if (it != evictions_per_class_.end()) {
86+
it->second += evicted;
87+
} else {
88+
evictions_per_class_[cid_id] = 0;
89+
}
7590
}
91+
7692
stats.numTraversals.inc();
7793
stats.numEvictedItems.add(evictions);
78-
stats.totalClasses.add(classes);
94+
stats.totalClasses.add(classes.size());
7995
}
8096

8197
template <typename CacheT>

cachelib/allocator/BackgroundEvictor.h

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -65,14 +65,15 @@ class BackgroundEvictor : public PeriodicWorker {
6565
// this tier
6666
// @param tier id memory tier to perform eviction on
6767
BackgroundEvictor(Cache& cache,
68-
std::shared_ptr<BackgroundEvictorStrategy> strategy,
69-
unsigned int tid);
68+
std::shared_ptr<BackgroundEvictorStrategy> strategy);
7069

7170
~BackgroundEvictor() override;
7271

7372
BackgroundEvictionStats getStats() const noexcept;
7473
std::map<uint32_t,uint64_t> getClassStats() const noexcept;
7574

75+
void setAssignedMemory(std::vector<std::tuple<TierId, PoolId, ClassId>> &&assignedMemory);
76+
7677
private:
7778
std::map<uint32_t,uint64_t> evictions_per_class_;
7879

@@ -82,13 +83,15 @@ class BackgroundEvictor : public PeriodicWorker {
8283

8384
Cache& cache_;
8485
std::shared_ptr<BackgroundEvictorStrategy> strategy_;
85-
unsigned int tid_;
8686

8787
// implements the actual logic of running the background evictor
8888
void work() override final;
89-
void checkAndRun(PoolId pid);
89+
void checkAndRun();
9090

9191
BackgroundEvictorStats stats;
92+
93+
std::vector<std::tuple<TierId, PoolId, ClassId>> assignedMemory_;
94+
folly::DistributedMutex mutex;
9295
};
9396
} // namespace cachelib
9497
} // namespace facebook

cachelib/allocator/CacheAllocator-inl.h

Lines changed: 78 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -329,7 +329,7 @@ void CacheAllocator<CacheTrait>::initWorkers() {
329329
if (config_.backgroundEvictorEnabled()) {
330330
startNewBackgroundEvictor(config_.backgroundEvictorInterval,
331331
config_.backgroundEvictorStrategy,
332-
0); //right now default to tier 0);
332+
config_.backgroundEvictorThreads);
333333
}
334334
}
335335

@@ -353,7 +353,7 @@ CacheAllocator<CacheTrait>::allocate(PoolId poolId,
353353
creationTime = util::getCurrentTimeSec();
354354
}
355355
return allocateInternal(poolId, key, size, creationTime,
356-
ttlSecs == 0 ? 0 : creationTime + ttlSecs);
356+
ttlSecs == 0 ? 0 : creationTime + ttlSecs, false);
357357
}
358358

359359
template <typename CacheTrait>
@@ -365,14 +365,24 @@ bool CacheAllocator<CacheTrait>::shouldWakeupBgEvictor(TierId tid, PoolId pid, C
365365
&& acAllocatedPercentage(tid, pid, cid) >= config_.lowEvictionAcWatermark;
366366
}
367367

368+
template <typename CacheTrait>
369+
size_t CacheAllocator<CacheTrait>::backgroundEvictorId(TierId tid, PoolId pid, ClassId cid)
370+
{
371+
XDCHECK(backgroundEvictor_.size());
372+
373+
// TODO: came up with some better sharding (use some hashing)
374+
return (tid + pid + cid) % backgroundEvictor_.size();
375+
}
376+
368377
template <typename CacheTrait>
369378
typename CacheAllocator<CacheTrait>::ItemHandle
370379
CacheAllocator<CacheTrait>::allocateInternalTier(TierId tid,
371380
PoolId pid,
372381
typename Item::Key key,
373382
uint32_t size,
374383
uint32_t creationTime,
375-
uint32_t expiryTime) {
384+
uint32_t expiryTime,
385+
bool fromEvictorThread) {
376386
util::LatencyTracker tracker{stats().allocateLatency_};
377387

378388
SCOPE_FAIL { stats_.invalidAllocs.inc(); };
@@ -388,8 +398,8 @@ CacheAllocator<CacheTrait>::allocateInternalTier(TierId tid,
388398

389399
void* memory = allocator_[tid]->allocate(pid, requiredSize);
390400

391-
if (backgroundEvictor_ && (memory == nullptr || shouldWakeupBgEvictor(tid, pid, cid))) {
392-
backgroundEvictor_->wakeUp();
401+
if (backgroundEvictor_.size() && !fromEvictorThread && (memory == nullptr || shouldWakeupBgEvictor(tid, pid, cid))) {
402+
backgroundEvictor_[backgroundEvictorId(tid, pid, cid)]->wakeUp();
393403
}
394404

395405
// TODO: Today disableEviction means do not evict from memory (DRAM).
@@ -447,7 +457,11 @@ double CacheAllocator<CacheTrait>::acAllocatedPercentage(TierId tid, PoolId pid,
447457
{
448458
const auto &ac = allocator_[tid]->getPool(pid).getAllocationClassFor(cid);
449459
auto acAllocatedSize = ac.currAllocSize_.load(std::memory_order_relaxed);
450-
auto acUsableSize = ac.curAllocatedSlabs_.load(std::memory_order_relaxed);
460+
auto acUsableSize = ac.curAllocatedSlabs_.load(std::memory_order_relaxed) * Slab::kSize;
461+
462+
if (acUsableSize == 0)
463+
return 0.0;
464+
451465
return 100.0 * static_cast<double>(acAllocatedSize) / static_cast<double>(acUsableSize);
452466
}
453467

@@ -528,9 +542,10 @@ CacheAllocator<CacheTrait>::allocateInternal(PoolId pid,
528542
typename Item::Key key,
529543
uint32_t size,
530544
uint32_t creationTime,
531-
uint32_t expiryTime) {
545+
uint32_t expiryTime,
546+
bool fromEvictorThread) {
532547
auto tid = getTargetTierForItem(pid, key, size, creationTime, expiryTime);
533-
return allocateInternalTier(tid, pid, key, size, creationTime, expiryTime);
548+
return allocateInternalTier(tid, pid, key, size, creationTime, expiryTime, fromEvictorThread);
534549
}
535550

536551
template <typename CacheTrait>
@@ -1708,8 +1723,7 @@ bool CacheAllocator<CacheTrait>::shouldEvictToNextMemoryTier(
17081723
template <typename CacheTrait>
17091724
typename CacheAllocator<CacheTrait>::WriteHandle
17101725
CacheAllocator<CacheTrait>::tryEvictToNextMemoryTier(
1711-
TierId tid, PoolId pid, Item& item) {
1712-
if(item.isChainedItem()) return {}; // TODO: We do not support ChainedItem yet
1726+
TierId tid, PoolId pid, Item& item, bool fromEvictorThread) {
17131727
if(item.isExpired()) return acquire(&item);
17141728

17151729
TierId nextTier = tid;
@@ -1722,7 +1736,8 @@ CacheAllocator<CacheTrait>::tryEvictToNextMemoryTier(
17221736
item.getKey(),
17231737
item.getSize(),
17241738
item.getCreationTime(),
1725-
item.getExpiryTime());
1739+
item.getExpiryTime(),
1740+
fromEvictorThread);
17261741

17271742
if (newItemHdl) {
17281743
XDCHECK_EQ(newItemHdl->getSize(), item.getSize());
@@ -1736,10 +1751,10 @@ CacheAllocator<CacheTrait>::tryEvictToNextMemoryTier(
17361751

17371752
template <typename CacheTrait>
17381753
typename CacheAllocator<CacheTrait>::WriteHandle
1739-
CacheAllocator<CacheTrait>::tryEvictToNextMemoryTier(Item& item) {
1754+
CacheAllocator<CacheTrait>::tryEvictToNextMemoryTier(Item& item, bool fromEvictorThread) {
17401755
auto tid = getTierId(item);
17411756
auto pid = allocator_[tid]->getAllocInfo(item.getMemory()).poolId;
1742-
return tryEvictToNextMemoryTier(tid, pid, item);
1757+
return tryEvictToNextMemoryTier(tid, pid, item, fromEvictorThread);
17431758
}
17441759

17451760
template <typename CacheTrait>
@@ -2382,6 +2397,11 @@ PoolId CacheAllocator<CacheTrait>::addPool(
23822397
setRebalanceStrategy(pid, std::move(rebalanceStrategy));
23832398
setResizeStrategy(pid, std::move(resizeStrategy));
23842399

2400+
if (backgroundEvictor_.size()) {
2401+
for (size_t id = 0; id < backgroundEvictor_.size(); id++)
2402+
backgroundEvictor_[id]->setAssignedMemory(getAssignedMemoryToBgEvictor(id));
2403+
}
2404+
23852405
return pid;
23862406
}
23872407

@@ -2504,7 +2524,7 @@ std::set<PoolId> CacheAllocator<CacheTrait>::getRegularPoolIds() const {
25042524
folly::SharedMutex::ReadHolder r(poolsResizeAndRebalanceLock_);
25052525
// TODO - get rid of the duplication - right now, each tier
25062526
// holds pool objects with mostly the same info
2507-
return filterCompactCachePools(allocator_[0]->getPoolIds());
2527+
return filterCompactCachePools(allocator_[currentTier()]->getPoolIds());
25082528
}
25092529

25102530
template <typename CacheTrait>
@@ -2868,7 +2888,8 @@ CacheAllocator<CacheTrait>::allocateNewItemForOldItem(const Item& oldItem) {
28682888
oldItem.getKey(),
28692889
oldItem.getSize(),
28702890
oldItem.getCreationTime(),
2871-
oldItem.getExpiryTime());
2891+
oldItem.getExpiryTime(),
2892+
false);
28722893
if (!newItemHdl) {
28732894
return {};
28742895
}
@@ -3001,14 +3022,14 @@ void CacheAllocator<CacheTrait>::evictForSlabRelease(
30013022
template <typename CacheTrait>
30023023
typename CacheAllocator<CacheTrait>::ItemHandle
30033024
CacheAllocator<CacheTrait>::evictNormalItem(Item& item,
3004-
bool skipIfTokenInvalid) {
3025+
bool skipIfTokenInvalid, bool fromEvictorThread) {
30053026
XDCHECK(item.isMoving());
30063027

30073028
if (item.isOnlyMoving()) {
30083029
return ItemHandle{};
30093030
}
30103031

3011-
auto evictHandle = tryEvictToNextMemoryTier(item);
3032+
auto evictHandle = tryEvictToNextMemoryTier(item, fromEvictorThread);
30123033
if(evictHandle) return evictHandle;
30133034

30143035
auto predicate = [](const Item& it) { return it.getRefCount() == 0; };
@@ -3811,12 +3832,43 @@ bool CacheAllocator<CacheTrait>::startNewReaper(
38113832
return startNewWorker("Reaper", reaper_, interval, reaperThrottleConfig);
38123833
}
38133834

3835+
template <typename CacheTrait>
3836+
auto CacheAllocator<CacheTrait>::getAssignedMemoryToBgEvictor(size_t evictorId)
3837+
{
3838+
std::vector<std::tuple<TierId, PoolId, ClassId>> asssignedMemory;
3839+
// TODO: for now, only evict from tier 0
3840+
for (TierId tid = 0; tid < 1; tid++) {
3841+
auto pools = filterCompactCachePools(allocator_[tid]->getPoolIds());
3842+
for (const auto pid : pools) {
3843+
const auto& mpStats = getPoolByTid(pid,tid).getStats();
3844+
for (const auto cid : mpStats.classIds) {
3845+
if (backgroundEvictorId(tid, pid, cid) == evictorId) {
3846+
asssignedMemory.emplace_back(tid, pid, cid);
3847+
}
3848+
}
3849+
}
3850+
}
3851+
return asssignedMemory;
3852+
}
3853+
38143854
template <typename CacheTrait>
38153855
bool CacheAllocator<CacheTrait>::startNewBackgroundEvictor(
38163856
std::chrono::milliseconds interval,
38173857
std::shared_ptr<BackgroundEvictorStrategy> strategy,
3818-
unsigned int tid ) {
3819-
return startNewWorker("BackgroundEvictor", backgroundEvictor_, interval, strategy, tid);
3858+
size_t threads) {
3859+
XDCHECK(threads > 0);
3860+
backgroundEvictor_.resize(threads);
3861+
bool result = true;
3862+
3863+
for (size_t i = 0; i < threads; i++) {
3864+
auto ret = startNewWorker("BackgroundEvictor" + std::to_string(i), backgroundEvictor_[i], interval, strategy);
3865+
result = result && ret;
3866+
3867+
if (result) {
3868+
backgroundEvictor_[i]->setAssignedMemory(getAssignedMemoryToBgEvictor(i));
3869+
}
3870+
}
3871+
return result;
38203872
}
38213873

38223874
template <typename CacheTrait>
@@ -3848,8 +3900,13 @@ bool CacheAllocator<CacheTrait>::stopReaper(std::chrono::seconds timeout) {
38483900

38493901
template <typename CacheTrait>
38503902
bool CacheAllocator<CacheTrait>::stopBackgroundEvictor(
3851-
std::chrono::seconds timeout) {
3852-
return stopWorker("BackgroundEvictor", backgroundEvictor_, timeout);
3903+
std::chrono::seconds timeout) {
3904+
bool result = true;
3905+
for (size_t i = 0; i < backgroundEvictor_.size(); i++) {
3906+
auto ret = stopWorker("BackgroundEvictor" + std::to_string(i), backgroundEvictor_[i], timeout);
3907+
result = result && ret;
3908+
}
3909+
return result;
38533910
}
38543911

38553912
template <typename CacheTrait>

0 commit comments

Comments
 (0)