1616
1717#pragma once
1818
19- #include " cachelib/allocator/BackgroundMoverStrategy .h"
19+ #include " cachelib/allocator/Cache .h"
2020#include " cachelib/allocator/CacheStats.h"
21- #include " cachelib/common/AtomicCounter.h"
2221#include " cachelib/common/PeriodicWorker.h"
2322
2423namespace facebook ::cachelib {
2524// wrapper that exposes the private APIs of CacheType that are specifically
2625// needed for the cache api
2726template <typename C>
2827struct BackgroundMoverAPIWrapper {
29- static size_t traverseAndEvictItems (C& cache,
30- unsigned int pid,
31- unsigned int cid,
32- size_t batch) {
33- return cache.traverseAndEvictItems (pid, cid, batch);
28+ // traverse the cache and move items from one tier to another
29+ // @param cache the cache interface
30+ // @param pid the pool id to traverse
31+ // @param cid the class id to traverse
32+ // @param evictionBatch number of items to evict in one go
33+ // @param promotionBatch number of items to promote in one go
34+ // @return pair of number of items evicted and promoted
35+ static std::pair<size_t , size_t > traverseAndMoveItems (C& cache,
36+ PoolId pid,
37+ ClassId cid,
38+ size_t evictionBatch,
39+ size_t promotionBatch) {
40+ return cache.traverseAndMoveItems (pid, cid, evictionBatch, promotionBatch);
3441 }
35-
36- static size_t traverseAndPromoteItems (C& cache,
37- unsigned int pid,
38- unsigned int cid,
39- size_t batch) {
40- return cache.traverseAndPromoteItems (pid, cid, batch);
42+ static std::pair<size_t , double > getApproxUsage (C& cache,
43+ PoolId pid,
44+ ClassId cid) {
45+ const auto & pool = cache.getPool (pid);
46+ // we wait until all slabs are allocated before we start evicting
47+ if (!pool.allSlabsAllocated ()) {
48+ return {0 , 0.0 };
49+ }
50+ return pool.getApproxUsage (cid);
4151 }
4252};
4353
44- enum class MoverDir { Evict = 0 , Promote };
45-
4654// Periodic worker that evicts items from tiers in batches
4755// The primary aim is to reduce insertion times for new items in the
4856// cache
4957template <typename CacheT>
5058class BackgroundMover : public PeriodicWorker {
5159 public:
60+ using ClassBgStatsType =
61+ std::map<MemoryDescriptorType, std::pair<size_t , size_t >>;
5262 using Cache = CacheT;
5363 // @param cache the cache interface
54- // @param strategy the stragey class that defines how objects are
55- // moved (promoted vs. evicted and how much)
64+ // @param evictionBatch number of items to evict in one go
65+ // @param promotionBatch number of items to promote in one go
66+ // @param targetFree target free percentage in the class
5667 BackgroundMover (Cache& cache,
57- std::shared_ptr<BackgroundMoverStrategy> strategy,
58- MoverDir direction_);
68+ size_t evictionBatch,
69+ size_t promotionBatch,
70+ double targetFree);
5971
6072 ~BackgroundMover () override ;
6173
6274 BackgroundMoverStats getStats () const noexcept ;
63- std::map<PoolId, std::map<ClassId, uint64_t >> getClassStats () const noexcept ;
75+ ClassBgStatsType getPerClassStats () const noexcept { return movesPerClass_; }
6476
6577 void setAssignedMemory (std::vector<MemoryDescriptorType>&& assignedMemory);
6678
@@ -69,40 +81,75 @@ class BackgroundMover : public PeriodicWorker {
6981 static size_t workerId (PoolId pid, ClassId cid, size_t numWorkers);
7082
7183 private:
72- std::map<PoolId, std::map<ClassId, uint64_t >> movesPerClass_;
84+ struct TraversalStats {
85+ // record a traversal over all assigned classes
86+ // and its time taken
87+ void recordTraversalTime (uint64_t nsTaken);
88+
89+ uint64_t getAvgTraversalTimeNs (uint64_t numTraversals) const ;
90+ uint64_t getMinTraversalTimeNs () const { return minTraversalTimeNs_; }
91+ uint64_t getMaxTraversalTimeNs () const { return maxTraversalTimeNs_; }
92+ uint64_t getLastTraversalTimeNs () const { return lastTraversalTimeNs_; }
93+
94+ private:
95+ // time it took us the last time to traverse the cache.
96+ uint64_t lastTraversalTimeNs_{0 };
97+ uint64_t minTraversalTimeNs_{std::numeric_limits<uint64_t >::max ()};
98+ uint64_t maxTraversalTimeNs_{0 };
99+ uint64_t totalTraversalTimeNs_{0 };
100+ };
101+
102+ TraversalStats traversalStats_;
73103 // cache allocator's interface for evicting
74104 using Item = typename Cache::Item;
75105
76106 Cache& cache_;
77- std::shared_ptr<BackgroundMoverStrategy> strategy_;
78- MoverDir direction_ ;
79-
80- std::function< size_t (Cache&, unsigned int , unsigned int , size_t )> moverFunc ;
107+ uint8_t numTiers_{ 1 }; // until we have multi-tier support
108+ size_t evictionBatch_{ 0 } ;
109+ size_t promotionBatch_{ 0 };
110+ double targetFree_{ 0.03 } ;
81111
82112 // implements the actual logic of running the background evictor
83113 void work () override final ;
84114 void checkAndRun ();
85115
86- AtomicCounter numMovedItems_{0 };
87- AtomicCounter numTraversals_{0 };
88- AtomicCounter totalBytesMoved_{0 };
116+ // populates the toFree map for each class with the number of items to free
117+ std::map<MemoryDescriptorType, size_t > getNumItemsToFree (
118+ const std::vector<MemoryDescriptorType>& assignedMemory);
119+
120+ uint64_t numEvictedItems_{0 };
121+ uint64_t numPromotedItems_{0 };
122+ uint64_t numTraversals_{0 };
123+
124+ ClassBgStatsType movesPerClass_;
89125
90126 std::vector<MemoryDescriptorType> assignedMemory_;
91127 folly::DistributedMutex mutex_;
92128};
93129
94130template <typename CacheT>
95- BackgroundMover<CacheT>::BackgroundMover(
96- Cache& cache,
97- std::shared_ptr<BackgroundMoverStrategy> strategy,
98- MoverDir direction)
99- : cache_(cache), strategy_(strategy), direction_(direction) {
100- if (direction_ == MoverDir::Evict) {
101- moverFunc = BackgroundMoverAPIWrapper<CacheT>::traverseAndEvictItems;
102-
103- } else if (direction_ == MoverDir::Promote) {
104- moverFunc = BackgroundMoverAPIWrapper<CacheT>::traverseAndPromoteItems;
105- }
131+ BackgroundMover<CacheT>::BackgroundMover(Cache& cache,
132+ size_t evictionBatch,
133+ size_t promotionBatch,
134+ double targetFree)
135+ : cache_(cache),
136+ evictionBatch_ (evictionBatch),
137+ promotionBatch_(promotionBatch),
138+ targetFree_(targetFree) {}
139+
140+ template <typename CacheT>
141+ void BackgroundMover<CacheT>::TraversalStats::recordTraversalTime(
142+ uint64_t nsTaken) {
143+ lastTraversalTimeNs_ = nsTaken;
144+ minTraversalTimeNs_ = std::min (minTraversalTimeNs_, nsTaken);
145+ maxTraversalTimeNs_ = std::max (maxTraversalTimeNs_, nsTaken);
146+ totalTraversalTimeNs_ += nsTaken;
147+ }
148+
149+ template <typename CacheT>
150+ uint64_t BackgroundMover<CacheT>::TraversalStats::getAvgTraversalTimeNs(
151+ uint64_t numTraversals) const {
152+ return numTraversals ? totalTraversalTimeNs_ / numTraversals : 0 ;
106153}
107154
108155template <typename CacheT>
@@ -132,50 +179,89 @@ void BackgroundMover<CacheT>::setAssignedMemory(
132179 });
133180}
134181
135- // Look for classes that exceed the target memory capacity
136- // and return those for eviction
182+ template <typename CacheT>
183+ std::map<MemoryDescriptorType, size_t >
184+ BackgroundMover<CacheT>::getNumItemsToFree(
185+ const std::vector<MemoryDescriptorType>& assignedMemory) {
186+ std::map<MemoryDescriptorType, size_t > toFree;
187+ for (const auto & md : assignedMemory) {
188+ const auto [pid, cid] = md;
189+ const auto & pool = cache_.getPool (pid);
190+ const auto [activeItems, usage] =
191+ BackgroundMoverAPIWrapper<CacheT>::getApproxUsage (cache_, pid, cid);
192+ if (usage < 1 - targetFree_) {
193+ toFree[md] = 0 ;
194+ } else {
195+ size_t maxItems = activeItems / usage;
196+ size_t targetItems = maxItems * (1 - targetFree_);
197+ size_t toFreeItems =
198+ activeItems > targetItems ? activeItems - targetItems : 0 ;
199+ toFree[md] = toFreeItems;
200+ }
201+ }
202+ return toFree;
203+ }
204+
137205template <typename CacheT>
138206void BackgroundMover<CacheT>::checkAndRun() {
139207 auto assignedMemory = mutex_.lock_combine ([this ] { return assignedMemory_; });
140-
141- unsigned int moves = 0 ;
142- auto batches = strategy_->calculateBatchSizes (cache_, assignedMemory);
143-
144- for (size_t i = 0 ; i < batches.size (); i++) {
145- const auto [pid, cid] = assignedMemory[i];
146- const auto batch = batches[i];
147-
148- if (batch == 0 ) {
149- continue ;
208+ auto toFree = getNumItemsToFree (assignedMemory); // calculate the number of
209+ // items to free
210+ while (true ) {
211+ bool allDone = true ;
212+ for (auto md : assignedMemory) {
213+ const auto [pid, cid] = md;
214+ size_t evictionBatch = evictionBatch_;
215+ size_t promotionBatch = 0 ; // will enable with multi-tier support
216+ if (toFree[md] == 0 ) {
217+ // no eviction work to be done since there is already at least
218+ // targetFree remaining in the class
219+ evictionBatch = 0 ;
220+ } else {
221+ allDone = false ; // we still have some items to free
222+ }
223+ if (promotionBatch + evictionBatch > 0 ) {
224+ const auto begin = util::getCurrentTimeNs ();
225+ // try moving BATCH items from the class in order to reach free target
226+ auto moved = BackgroundMoverAPIWrapper<CacheT>::traverseAndMoveItems (
227+ cache_, pid, cid, evictionBatch, promotionBatch);
228+ numEvictedItems_ += moved.first ;
229+ toFree[md] > moved.first ? toFree[md] -= moved.first : toFree[md] = 0 ;
230+ numPromotedItems_ += moved.second ;
231+ auto curr = movesPerClass_[md];
232+ curr.first += moved.first ;
233+ curr.second += moved.second ;
234+ movesPerClass_[md] = curr;
235+ numTraversals_++;
236+ auto end = util::getCurrentTimeNs ();
237+ traversalStats_.recordTraversalTime (end > begin ? end - begin : 0 );
238+ }
239+ }
240+ if (shouldStopWork () || allDone) {
241+ break ;
150242 }
151-
152- // try moving BATCH items from the class in order to reach free target
153- auto moved = moverFunc (cache_, pid, cid, batch);
154- moves += moved;
155- movesPerClass_[pid][cid] += moved;
156- totalBytesMoved_.add (moved * cache_.getPool (pid).getAllocSizes ()[cid]);
157243 }
158-
159- numTraversals_.inc ();
160- numMovedItems_.add (moves);
161244}
162245
163246template <typename CacheT>
164247BackgroundMoverStats BackgroundMover<CacheT>::getStats() const noexcept {
165248 BackgroundMoverStats stats;
166- stats.numMovedItems = numMovedItems_.get ();
167- stats.runCount = numTraversals_.get ();
168- stats.totalBytesMoved = totalBytesMoved_.get ();
249+ stats.numEvictedItems = numEvictedItems_;
250+ stats.numPromotedItems = numPromotedItems_;
251+ stats.numTraversals = numTraversals_;
252+ stats.runCount = getRunCount ();
253+ stats.avgItemsMoved =
254+ (double )(stats.numEvictedItems + stats.numPromotedItems ) /
255+ (double )numTraversals_;
256+ stats.lastTraversalTimeNs = traversalStats_.getLastTraversalTimeNs ();
257+ stats.avgTraversalTimeNs =
258+ traversalStats_.getAvgTraversalTimeNs (numTraversals_);
259+ stats.minTraversalTimeNs = traversalStats_.getMinTraversalTimeNs ();
260+ stats.maxTraversalTimeNs = traversalStats_.getMaxTraversalTimeNs ();
169261
170262 return stats;
171263}
172264
173- template <typename CacheT>
174- std::map<PoolId, std::map<ClassId, uint64_t >>
175- BackgroundMover<CacheT>::getClassStats() const noexcept {
176- return movesPerClass_;
177- }
178-
179265template <typename CacheT>
180266size_t BackgroundMover<CacheT>::workerId(PoolId pid,
181267 ClassId cid,
@@ -185,4 +271,4 @@ size_t BackgroundMover<CacheT>::workerId(PoolId pid,
185271 // TODO: came up with some better sharding (use hashing?)
186272 return (pid + cid) % numWorkers;
187273}
188- } // namespace facebook::cachelib
274+ }; // namespace facebook::cachelib
0 commit comments