Skip to content

Commit a3e29dd

Browse files
committed
Add extra option for forcing earlier eviction
and add a simple unit test
1 parent 4c3eeab commit a3e29dd

9 files changed

+72
-7
lines changed

Diff for: MultiTierDataMovement.md

+2
Original file line numberDiff line numberDiff line change
@@ -103,6 +103,8 @@ and `minAcAllocationWatermark`: then extra checks (described below) are performe
103103

104104
By default, allocation will always be performed from the upper tier.
105105

106+
- `acTopTierEvictionWatermark`: If there is less that this percent of free memory in topmost tier, cachelib will attempt to evict from top tier. This option takes precedence before allocationWatermarks.
107+
106108
### Extra policies (used only when percentage of free AllocationClasses is between `maxAcAllocationWatermark`
107109
and `minAcAllocationWatermark`)
108110
- `sizeThresholdPolicy`: If item is smaller than this value, always allocate it in upper tier.

Diff for: cachelib/allocator/CacheAllocator-inl.h

+17-5
Original file line numberDiff line numberDiff line change
@@ -419,7 +419,19 @@ CacheAllocator<CacheTrait>::allocateInternalTier(TierId tid,
419419
// TODO: per-tier
420420
(*stats_.allocAttempts)[pid][cid].inc();
421421

422-
void* memory = allocator_[tid]->allocate(pid, requiredSize);
422+
void *memory = nullptr;
423+
424+
if (tid == 0 && config_.acTopTierEvictionWatermark > 0.0
425+
&& getAllocationClassStats(tid, pid, cid)
426+
.approxFreePercent < config_.acTopTierEvictionWatermark) {
427+
memory = findEviction(tid, pid, cid);
428+
}
429+
430+
if (memory == nullptr) {
431+
// TODO: should we try allocate item even if this will result in violating
432+
// acTopTierEvictionWatermark?
433+
memory = allocator_[tid]->allocate(pid, requiredSize);
434+
}
423435

424436
if (backgroundEvictor_.size() && !fromEvictorThread && (memory == nullptr || shouldWakeupBgEvictor(tid, pid, cid))) {
425437
backgroundEvictor_[backgroundWorkerId(tid, pid, cid, backgroundEvictor_.size())]->wakeUp();
@@ -1580,7 +1592,7 @@ CacheAllocator<CacheTrait>::tryEvictToNextMemoryTier(
15801592

15811593
if (newItemHdl) {
15821594
XDCHECK_EQ(newItemHdl->getSize(), item.getSize());
1583-
if (tryMovingForSlabRelease(item, newItemHdl, itemMovingPredicate)) {
1595+
if (tryMovingItem(item, newItemHdl, itemMovingPredicate)) {
15841596
return acquire(&item);
15851597
}
15861598
}
@@ -1612,7 +1624,7 @@ CacheAllocator<CacheTrait>::tryPromoteToNextMemoryTier(
16121624
// if inclusive cache is allowed, replace even if there are active users.
16131625
return config_.numDuplicateElements > 0 || item.getRefCount() == 0;
16141626
};
1615-
if (tryMovingForSlabRelease(item, newItemHdl, std::move(predicate))) {
1627+
if (tryMovingItem(item, newItemHdl, std::move(predicate))) {
16161628
return true;
16171629
}
16181630
}
@@ -2728,7 +2740,7 @@ bool CacheAllocator<CacheTrait>::moveForSlabRelease(
27282740

27292741
// if we have a valid handle, try to move, if not, we retry.
27302742
if (newItemHdl) {
2731-
isMoved = tryMovingForSlabRelease(oldItem, newItemHdl, itemMovingPredicate);
2743+
isMoved = tryMovingItem(oldItem, newItemHdl, itemMovingPredicate);
27322744
if (isMoved) {
27332745
break;
27342746
}
@@ -2852,7 +2864,7 @@ CacheAllocator<CacheTrait>::allocateNewItemForOldItem(const Item& oldItem) {
28522864

28532865
template <typename CacheTrait>
28542866
template <typename Predicate>
2855-
bool CacheAllocator<CacheTrait>::tryMovingForSlabRelease(
2867+
bool CacheAllocator<CacheTrait>::tryMovingItem(
28562868
Item& oldItem, ItemHandle& newItemHdl, Predicate&& predicate) {
28572869
// By holding onto a user-level synchronization object, we ensure moving
28582870
// a regular item or chained item is synchronized with any potential

Diff for: cachelib/allocator/CacheAllocator.h

+1-1
Original file line numberDiff line numberDiff line change
@@ -1769,7 +1769,7 @@ class CacheAllocator : public CacheBase {
17691769
// @return true if the item has been moved
17701770
// false if we have exhausted moving attempts
17711771
template <typename Predicate>
1772-
bool tryMovingForSlabRelease(Item& item, ItemHandle& newItemHdl, Predicate &&predicate);
1772+
bool tryMovingItem(Item& item, ItemHandle& newItemHdl, Predicate &&predicate);
17731773

17741774
// Evict an item from access and mm containers and
17751775
// ensure it is safe for freeing.

Diff for: cachelib/allocator/CacheAllocatorConfig.h

+1
Original file line numberDiff line numberDiff line change
@@ -625,6 +625,7 @@ class CacheAllocatorConfig {
625625
double highEvictionAcWatermark{5.0};
626626
double minAcAllocationWatermark{0.0};
627627
double maxAcAllocationWatermark{0.0};
628+
double acTopTierEvictionWatermark{0.0}; // TODO: make it per TIER?
628629
uint64_t sizeThresholdPolicy{0};
629630
double defaultTierChancePercentage{50.0};
630631
// TODO: default could be based on ratio

Diff for: cachelib/allocator/tests/AllocatorMemoryTiersTest.cpp

+1
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ TEST_F(LruAllocatorMemoryTiersTest, MultiTiersValid) { this->testMultiTiersValid
2828
TEST_F(LruAllocatorMemoryTiersTest, MultiTiersValidMixed) { this->testMultiTiersValidMixed(); }
2929
TEST_F(LruAllocatorMemoryTiersTest, MultiTiersForceTierAllocation) { this->testMultiTiersForceTierAllocation(); }
3030
TEST_F(LruAllocatorMemoryTiersTest, MultiTiersWatermarkTierAllocation) { this->testMultiTiersWatermarkAllocation(); }
31+
TEST_F(LruAllocatorMemoryTiersTest, MultiTiersSyncEviction) { this->testSyncEviction(); }
3132

3233
} // end of namespace tests
3334
} // end of namespace cachelib

Diff for: cachelib/allocator/tests/AllocatorMemoryTiersTest.h

+46
Original file line numberDiff line numberDiff line change
@@ -123,6 +123,52 @@ class AllocatorMemoryTiersTest : public AllocatorTest<AllocatorT> {
123123
}
124124
}
125125

126+
void testSyncEviction() {
127+
auto config = makeDefaultConfig();
128+
config.setCacheSize(10 * Slab::kSize);
129+
config.acTopTierEvictionWatermark = 99.0;
130+
131+
{
132+
AllocatorT alloc(AllocatorT::SharedMemNew, config);
133+
auto pool = alloc.addPool("default", alloc.getCacheMemoryStats().cacheSize);
134+
135+
{
136+
// should be allocated in upper tier.
137+
auto handle = alloc.allocate(pool, "key", Slab::kSize / 2);
138+
ASSERT_NE(handle, nullptr);
139+
std::string data = "some data";
140+
std::memcpy(handle->getMemory(), data.data(), data.size());
141+
142+
alloc.insertOrReplace(handle);
143+
144+
auto found = alloc.find("key");
145+
ASSERT_NE(found, nullptr);
146+
ASSERT_EQ(found->getSize(), Slab::kSize / 2);
147+
ASSERT_EQ(std::string(reinterpret_cast<const char*>(found->getMemory()), data.size()), data);
148+
}
149+
150+
auto toptier_free = alloc.getCacheMemoryStats().slabsApproxFreePercentages[0];
151+
ASSERT_NE(toptier_free, 100.0);
152+
ASSERT_EQ(alloc.getCacheMemoryStats().slabsApproxFreePercentages[1], 100.0);
153+
154+
auto handle2 = alloc.allocate(pool, "key2", Slab::kSize / 2);
155+
ASSERT_NE(handle2, nullptr);
156+
std::string data2 = "other data";
157+
std::memcpy(reinterpret_cast<char*>(handle2->getMemory()), data2.data(), data2.size());
158+
159+
alloc.insertOrReplace(handle2);
160+
161+
auto found2 = alloc.find("key2");
162+
ASSERT_NE(found2, nullptr);
163+
ASSERT_EQ(found2->getSize(), Slab::kSize / 2);
164+
ASSERT_EQ(std::string(reinterpret_cast<const char*>(found2->getMemory()), data2.size()), data2);
165+
166+
// previous data should be evicted, and replaced by new one
167+
ASSERT_EQ(toptier_free, alloc.getCacheMemoryStats().slabsApproxFreePercentages[0]);
168+
ASSERT_NE(alloc.getCacheMemoryStats().slabsApproxFreePercentages[1], 100.0);
169+
}
170+
}
171+
126172
void testMultiTiersWatermarkAllocation() {
127173
auto config = makeDefaultConfig();
128174

Diff for: cachelib/cachebench/cache/Cache-inl.h

+1
Original file line numberDiff line numberDiff line change
@@ -292,6 +292,7 @@ Cache<Allocator>::Cache(const CacheConfig& config,
292292
allocatorConfig_.numDuplicateElements = config_.numDuplicateElements;
293293
allocatorConfig_.syncPromotion = config_.syncPromotion;
294294
allocatorConfig_.promotionAcWatermark = config_.promotionAcWatermark;
295+
allocatorConfig_.acTopTierEvictionWatermark = config_.acTopTierEvictionWatermark;
295296

296297
if (!allocatorConfig_.cacheDir.empty()) {
297298
cache_ =

Diff for: cachelib/cachebench/util/CacheConfig.cpp

+2-1
Original file line numberDiff line numberDiff line change
@@ -105,6 +105,7 @@ CacheConfig::CacheConfig(const folly::dynamic& configJson) {
105105
JSONSetVal(configJson, highEvictionAcWatermark);
106106
JSONSetVal(configJson, minAcAllocationWatermark);
107107
JSONSetVal(configJson, maxAcAllocationWatermark);
108+
JSONSetVal(configJson, acTopTierEvictionWatermark);
108109
JSONSetVal(configJson, sizeThresholdPolicy);
109110
JSONSetVal(configJson, defaultTierChancePercentage);
110111
JSONSetVal(configJson, numDuplicateElements);
@@ -131,7 +132,7 @@ CacheConfig::CacheConfig(const folly::dynamic& configJson) {
131132
// if you added new fields to the configuration, update the JSONSetVal
132133
// to make them available for the json configs and increment the size
133134
// below
134-
checkCorrectSize<CacheConfig, 936>();
135+
checkCorrectSize<CacheConfig, 944>();
135136

136137
if (numPools != poolSizes.size()) {
137138
throw std::invalid_argument(folly::sformat(

Diff for: cachelib/cachebench/util/CacheConfig.h

+1
Original file line numberDiff line numberDiff line change
@@ -298,6 +298,7 @@ struct CacheConfig : public JSONConfig {
298298
double highEvictionAcWatermark{5.0};
299299
double minAcAllocationWatermark{0.0};
300300
double maxAcAllocationWatermark{0.0};
301+
double acTopTierEvictionWatermark{0.0};
301302
uint64_t sizeThresholdPolicy{0};
302303
double defaultTierChancePercentage{50.0};
303304
// TODO: default could be based on ratio

0 commit comments

Comments
 (0)