16
16
17
17
#pragma once
18
18
19
- #include " cachelib/allocator/BackgroundMoverStrategy .h"
19
+ #include " cachelib/allocator/Cache .h"
20
20
#include " cachelib/allocator/CacheStats.h"
21
- #include " cachelib/common/AtomicCounter.h"
22
21
#include " cachelib/common/PeriodicWorker.h"
23
22
24
23
namespace facebook ::cachelib {
25
24
// wrapper that exposes the private APIs of CacheType that are specifically
26
25
// needed for the cache api
27
26
template <typename C>
28
27
struct 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);
34
41
}
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);
41
51
}
42
52
};
43
53
44
- enum class MoverDir { Evict = 0 , Promote };
45
-
46
54
// Periodic worker that evicts items from tiers in batches
47
55
// The primary aim is to reduce insertion times for new items in the
48
56
// cache
49
57
template <typename CacheT>
50
58
class BackgroundMover : public PeriodicWorker {
51
59
public:
60
+ using ClassBgStatsType =
61
+ std::map<MemoryDescriptorType, std::pair<size_t , size_t >>;
52
62
using Cache = CacheT;
53
63
// @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
56
67
BackgroundMover (Cache& cache,
57
- std::shared_ptr<BackgroundMoverStrategy> strategy,
58
- MoverDir direction_);
68
+ size_t evictionBatch,
69
+ size_t promotionBatch,
70
+ double targetFree);
59
71
60
72
~BackgroundMover () override ;
61
73
62
74
BackgroundMoverStats getStats () const noexcept ;
63
- std::map<PoolId, std::map<ClassId, uint64_t >> getClassStats () const noexcept ;
75
+ ClassBgStatsType getPerClassStats () const noexcept { return movesPerClass_; }
64
76
65
77
void setAssignedMemory (std::vector<MemoryDescriptorType>&& assignedMemory);
66
78
@@ -69,40 +81,75 @@ class BackgroundMover : public PeriodicWorker {
69
81
static size_t workerId (PoolId pid, ClassId cid, size_t numWorkers);
70
82
71
83
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_;
73
103
// cache allocator's interface for evicting
74
104
using Item = typename Cache::Item;
75
105
76
106
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 } ;
81
111
82
112
// implements the actual logic of running the background evictor
83
113
void work () override final ;
84
114
void checkAndRun ();
85
115
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_;
89
125
90
126
std::vector<MemoryDescriptorType> assignedMemory_;
91
127
folly::DistributedMutex mutex_;
92
128
};
93
129
94
130
template <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 ;
106
153
}
107
154
108
155
template <typename CacheT>
@@ -132,50 +179,89 @@ void BackgroundMover<CacheT>::setAssignedMemory(
132
179
});
133
180
}
134
181
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
+
137
205
template <typename CacheT>
138
206
void BackgroundMover<CacheT>::checkAndRun() {
139
207
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 ;
150
242
}
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]);
157
243
}
158
-
159
- numTraversals_.inc ();
160
- numMovedItems_.add (moves);
161
244
}
162
245
163
246
template <typename CacheT>
164
247
BackgroundMoverStats BackgroundMover<CacheT>::getStats() const noexcept {
165
248
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 ();
169
261
170
262
return stats;
171
263
}
172
264
173
- template <typename CacheT>
174
- std::map<PoolId, std::map<ClassId, uint64_t >>
175
- BackgroundMover<CacheT>::getClassStats() const noexcept {
176
- return movesPerClass_;
177
- }
178
-
179
265
template <typename CacheT>
180
266
size_t BackgroundMover<CacheT>::workerId(PoolId pid,
181
267
ClassId cid,
@@ -185,4 +271,4 @@ size_t BackgroundMover<CacheT>::workerId(PoolId pid,
185
271
// TODO: came up with some better sharding (use hashing?)
186
272
return (pid + cid) % numWorkers;
187
273
}
188
- } // namespace facebook::cachelib
274
+ }; // namespace facebook::cachelib
0 commit comments