Skip to content

Commit 681bcbc

Browse files
authored
Merge pull request #73 from igchor/optimize_mmcontainer_locking_hetero
Reduce critical section in findEviction (for heterogenous memory cache)
2 parents dcf4290 + ed2af50 commit 681bcbc

File tree

11 files changed

+143
-244
lines changed

11 files changed

+143
-244
lines changed

cachelib/allocator/CacheAllocator-inl.h

Lines changed: 85 additions & 194 deletions
Original file line numberDiff line numberDiff line change
@@ -415,7 +415,7 @@ CacheAllocator<CacheTrait>::allocateInternalTier(TierId tid,
415415
}
416416

417417
template <typename CacheTrait>
418-
typename CacheAllocator<CacheTrait>::ItemHandle
418+
typename CacheAllocator<CacheTrait>::WriteHandle
419419
CacheAllocator<CacheTrait>::allocateInternal(PoolId pid,
420420
typename Item::Key key,
421421
uint32_t size,
@@ -1186,14 +1186,13 @@ bool CacheAllocator<CacheTrait>::addWaitContextForMovingItem(
11861186
}
11871187

11881188
template <typename CacheTrait>
1189-
template <typename ItemPtr>
11901189
typename CacheAllocator<CacheTrait>::ItemHandle
11911190
CacheAllocator<CacheTrait>::moveRegularItemOnEviction(
1192-
ItemPtr& oldItemPtr, ItemHandle& newItemHdl) {
1191+
Item& oldItem, ItemHandle& newItemHdl) {
1192+
XDCHECK(oldItem.isMoving());
11931193
// TODO: should we introduce new latency tracker. E.g. evictRegularLatency_
11941194
// ??? util::LatencyTracker tracker{stats_.evictRegularLatency_};
11951195

1196-
Item& oldItem = *oldItemPtr;
11971196
if (!oldItem.isAccessible() || oldItem.isExpired()) {
11981197
return {};
11991198
}
@@ -1249,7 +1248,7 @@ CacheAllocator<CacheTrait>::moveRegularItemOnEviction(
12491248
// it is unsafe to replace the old item with a new one, so we should
12501249
// also abort.
12511250
if (!accessContainer_->replaceIf(oldItem, *newItemHdl,
1252-
itemEvictionPredicate)) {
1251+
itemMovingPredicate)) {
12531252
return {};
12541253
}
12551254

@@ -1269,7 +1268,7 @@ CacheAllocator<CacheTrait>::moveRegularItemOnEviction(
12691268

12701269
// Inside the MM container's lock, this checks if the old item exists to
12711270
// make sure that no other thread removed it, and only then replaces it.
1272-
if (!replaceInMMContainer(oldItemPtr, *newItemHdl)) {
1271+
if (!replaceInMMContainer(oldItem, *newItemHdl)) {
12731272
accessContainer_->remove(*newItemHdl);
12741273
return {};
12751274
}
@@ -1453,42 +1452,64 @@ CacheAllocator<CacheTrait>::findEviction(TierId tid, PoolId pid, ClassId cid) {
14531452
// Keep searching for a candidate until we were able to evict it
14541453
// or until the search limit has been exhausted
14551454
unsigned int searchTries = 0;
1456-
auto itr = mmContainer.getEvictionIterator();
14571455
while ((config_.evictionSearchTries == 0 ||
1458-
config_.evictionSearchTries > searchTries) &&
1459-
itr) {
1456+
config_.evictionSearchTries > searchTries)) {
14601457
++searchTries;
14611458

1462-
Item* candidate = itr.get();
1459+
Item* toRecycle = nullptr;
1460+
Item* candidate = nullptr;
1461+
1462+
mmContainer.withEvictionIterator([this, &candidate, &toRecycle, &searchTries](auto &&itr){
1463+
while ((config_.evictionSearchTries == 0 ||
1464+
config_.evictionSearchTries > searchTries) && itr) {
1465+
++searchTries;
1466+
1467+
auto *toRecycle_ = itr.get();
1468+
auto *candidate_ = toRecycle_->isChainedItem()
1469+
? &toRecycle_->asChainedItem().getParentItem(compressor_)
1470+
: toRecycle_;
1471+
1472+
// make sure no other thead is evicting the item
1473+
if (candidate_->getRefCount() == 0 && candidate_->markMoving()) {
1474+
toRecycle = toRecycle_;
1475+
candidate = candidate_;
1476+
return;
1477+
}
1478+
1479+
++itr;
1480+
}
1481+
});
1482+
1483+
if (!toRecycle)
1484+
continue;
1485+
1486+
XDCHECK(toRecycle);
1487+
XDCHECK(candidate);
1488+
14631489
// for chained items, the ownership of the parent can change. We try to
14641490
// evict what we think as parent and see if the eviction of parent
14651491
// recycles the child we intend to.
1466-
1467-
ItemHandle toReleaseHandle = tryEvictToNextMemoryTier(tid, pid, itr);
1468-
bool movedToNextTier = false;
1469-
if(toReleaseHandle) {
1470-
movedToNextTier = true;
1471-
} else {
1472-
toReleaseHandle =
1473-
itr->isChainedItem()
1474-
? advanceIteratorAndTryEvictChainedItem(tid, pid, itr)
1475-
: advanceIteratorAndTryEvictRegularItem(tid, pid, mmContainer, itr);
1476-
}
1492+
auto toReleaseHandle =
1493+
evictNormalItem(*candidate, true /* skipIfTokenInvalid */);
1494+
auto ref = candidate->unmarkMoving();
14771495

1478-
if (toReleaseHandle) {
1479-
if (toReleaseHandle->hasChainedItem()) {
1496+
if (toReleaseHandle || ref == 0u) {
1497+
if (candidate->hasChainedItem()) {
14801498
(*stats_.chainedItemEvictions)[pid][cid].inc();
14811499
} else {
14821500
(*stats_.regularItemEvictions)[pid][cid].inc();
14831501
}
1502+
} else {
1503+
if (candidate->hasChainedItem()) {
1504+
stats_.evictFailParentAC.inc();
1505+
} else {
1506+
stats_.evictFailAC.inc();
1507+
}
1508+
}
14841509

1485-
// Invalidate iterator since later on we may use this mmContainer
1486-
// again, which cannot be done unless we drop this iterator
1487-
itr.destroy();
1488-
1489-
// we must be the last handle and for chained items, this will be
1490-
// the parent.
1491-
XDCHECK(toReleaseHandle.get() == candidate || candidate->isChainedItem());
1510+
if (toReleaseHandle) {
1511+
XDCHECK(toReleaseHandle.get() == candidate);
1512+
XDCHECK(toRecycle == candidate || toRecycle->isChainedItem());
14921513
XDCHECK_EQ(1u, toReleaseHandle->getRefCount());
14931514

14941515
// We manually release the item here because we don't want to
@@ -1504,15 +1525,18 @@ CacheAllocator<CacheTrait>::findEviction(TierId tid, PoolId pid, ClassId cid) {
15041525
// recycle the candidate.
15051526
if (ReleaseRes::kRecycled ==
15061527
releaseBackToAllocator(itemToRelease, RemoveContext::kEviction,
1507-
/* isNascent */ movedToNextTier, candidate)) {
1508-
return candidate;
1528+
/* isNascent */ false, toRecycle)) {
1529+
return toRecycle;
1530+
}
1531+
} else if (ref == 0u) {
1532+
// it's safe to recycle the item here as there are no more
1533+
// references and the item could not been marked as moving
1534+
// by other thread since it's detached from MMContainer.
1535+
if (ReleaseRes::kRecycled ==
1536+
releaseBackToAllocator(*candidate, RemoveContext::kEviction,
1537+
/* isNascent */ false, toRecycle)) {
1538+
return toRecycle;
15091539
}
1510-
}
1511-
1512-
// If we destroyed the itr to possibly evict and failed, we restart
1513-
// from the beginning again
1514-
if (!itr) {
1515-
itr.resetToBegin();
15161540
}
15171541
}
15181542
return nullptr;
@@ -1567,24 +1591,23 @@ bool CacheAllocator<CacheTrait>::shouldWriteToNvmCacheExclusive(
15671591
}
15681592

15691593
template <typename CacheTrait>
1570-
template <typename ItemPtr>
1571-
typename CacheAllocator<CacheTrait>::ItemHandle
1594+
typename CacheAllocator<CacheTrait>::WriteHandle
15721595
CacheAllocator<CacheTrait>::tryEvictToNextMemoryTier(
1573-
TierId tid, PoolId pid, ItemPtr& item) {
1574-
if(item->isChainedItem()) return {}; // TODO: We do not support ChainedItem yet
1575-
if(item->isExpired()) return acquire(item);
1596+
TierId tid, PoolId pid, Item& item) {
1597+
if(item.isChainedItem()) return {}; // TODO: We do not support ChainedItem yet
1598+
if(item.isExpired()) return acquire(&item);
15761599

15771600
TierId nextTier = tid; // TODO - calculate this based on some admission policy
15781601
while (++nextTier < numTiers_) { // try to evict down to the next memory tiers
15791602
// allocateInternal might trigger another eviction
15801603
auto newItemHdl = allocateInternalTier(nextTier, pid,
1581-
item->getKey(),
1582-
item->getSize(),
1583-
item->getCreationTime(),
1584-
item->getExpiryTime());
1604+
item.getKey(),
1605+
item.getSize(),
1606+
item.getCreationTime(),
1607+
item.getExpiryTime());
15851608

15861609
if (newItemHdl) {
1587-
XDCHECK_EQ(newItemHdl->getSize(), item->getSize());
1610+
XDCHECK_EQ(newItemHdl->getSize(), item.getSize());
15881611

15891612
return moveRegularItemOnEviction(item, newItemHdl);
15901613
}
@@ -1594,149 +1617,11 @@ CacheAllocator<CacheTrait>::tryEvictToNextMemoryTier(
15941617
}
15951618

15961619
template <typename CacheTrait>
1597-
typename CacheAllocator<CacheTrait>::ItemHandle
1598-
CacheAllocator<CacheTrait>::tryEvictToNextMemoryTier(Item* item) {
1599-
auto tid = getTierId(*item);
1600-
auto pid = allocator_[tid]->getAllocInfo(item->getMemory()).poolId;
1601-
return tryEvictToNextMemoryTier(tid, pid, item);
1602-
}
1603-
1604-
template <typename CacheTrait>
1605-
typename CacheAllocator<CacheTrait>::ItemHandle
1606-
CacheAllocator<CacheTrait>::advanceIteratorAndTryEvictRegularItem(
1607-
TierId tid, PoolId pid, MMContainer& mmContainer, EvictionIterator& itr) {
1608-
Item& item = *itr;
1609-
1610-
const bool evictToNvmCache = shouldWriteToNvmCache(item);
1611-
1612-
auto token = evictToNvmCache ? nvmCache_->createPutToken(item.getKey())
1613-
: typename NvmCacheT::PutToken{};
1614-
// record the in-flight eviciton. If not, we move on to next item to avoid
1615-
// stalling eviction.
1616-
if (evictToNvmCache && !token.isValid()) {
1617-
++itr;
1618-
stats_.evictFailConcurrentFill.inc();
1619-
return ItemHandle{};
1620-
}
1621-
1622-
// If there are other accessors, we should abort. Acquire a handle here since
1623-
// if we remove the item from both access containers and mm containers
1624-
// below, we will need a handle to ensure proper cleanup in case we end up
1625-
// not evicting this item
1626-
auto evictHandle = accessContainer_->removeIf(item, &itemEvictionPredicate);
1627-
1628-
if (!evictHandle) {
1629-
++itr;
1630-
stats_.evictFailAC.inc();
1631-
return evictHandle;
1632-
}
1633-
1634-
mmContainer.remove(itr);
1635-
XDCHECK_EQ(reinterpret_cast<uintptr_t>(evictHandle.get()),
1636-
reinterpret_cast<uintptr_t>(&item));
1637-
XDCHECK(!evictHandle->isInMMContainer());
1638-
XDCHECK(!evictHandle->isAccessible());
1639-
1640-
// If the item is now marked as moving, that means its corresponding slab is
1641-
// being released right now. So, we look for the next item that is eligible
1642-
// for eviction. It is safe to destroy the handle here since the moving bit
1643-
// is set. Iterator was already advance by the remove call above.
1644-
if (evictHandle->isMoving()) {
1645-
stats_.evictFailMove.inc();
1646-
return ItemHandle{};
1647-
}
1648-
1649-
// Invalidate iterator since later on if we are not evicting this
1650-
// item, we may need to rely on the handle we created above to ensure
1651-
// proper cleanup if the item's raw refcount has dropped to 0.
1652-
// And since this item may be a parent item that has some child items
1653-
// in this very same mmContainer, we need to make sure we drop this
1654-
// exclusive iterator so we can gain access to it when we're cleaning
1655-
// up the child items
1656-
itr.destroy();
1657-
1658-
// Ensure that there are no accessors after removing from the access
1659-
// container
1660-
XDCHECK(evictHandle->getRefCount() == 1);
1661-
1662-
if (evictToNvmCache && shouldWriteToNvmCacheExclusive(item)) {
1663-
XDCHECK(token.isValid());
1664-
nvmCache_->put(evictHandle, std::move(token));
1665-
}
1666-
return evictHandle;
1667-
}
1668-
1669-
template <typename CacheTrait>
1670-
typename CacheAllocator<CacheTrait>::ItemHandle
1671-
CacheAllocator<CacheTrait>::advanceIteratorAndTryEvictChainedItem(
1672-
TierId tid, PoolId pid, EvictionIterator& itr) {
1673-
XDCHECK(itr->isChainedItem());
1674-
1675-
ChainedItem* candidate = &itr->asChainedItem();
1676-
++itr;
1677-
1678-
// The parent could change at any point through transferChain. However, if
1679-
// that happens, we would realize that the releaseBackToAllocator return
1680-
// kNotRecycled and we would try another chained item, leading to transient
1681-
// failure.
1682-
auto& parent = candidate->getParentItem(compressor_);
1683-
1684-
const bool evictToNvmCache = shouldWriteToNvmCache(parent);
1685-
1686-
auto token = evictToNvmCache ? nvmCache_->createPutToken(parent.getKey())
1687-
: typename NvmCacheT::PutToken{};
1688-
1689-
// if token is invalid, return. iterator is already advanced.
1690-
if (evictToNvmCache && !token.isValid()) {
1691-
stats_.evictFailConcurrentFill.inc();
1692-
return ItemHandle{};
1693-
}
1694-
1695-
// check if the parent exists in the hashtable and refcount is drained.
1696-
auto parentHandle =
1697-
accessContainer_->removeIf(parent, &itemEvictionPredicate);
1698-
if (!parentHandle) {
1699-
stats_.evictFailParentAC.inc();
1700-
return parentHandle;
1701-
}
1702-
1703-
// Invalidate iterator since later on we may use the mmContainer
1704-
// associated with this iterator which cannot be done unless we
1705-
// drop this iterator
1706-
//
1707-
// This must be done once we know the parent is not nullptr.
1708-
// Since we can very well be the last holder of this parent item,
1709-
// which may have a chained item that is linked in this MM container.
1710-
itr.destroy();
1711-
1712-
// Ensure we have the correct parent and we're the only user of the
1713-
// parent, then free it from access container. Otherwise, we abort
1714-
XDCHECK_EQ(reinterpret_cast<uintptr_t>(&parent),
1715-
reinterpret_cast<uintptr_t>(parentHandle.get()));
1716-
XDCHECK_EQ(1u, parent.getRefCount());
1717-
1718-
removeFromMMContainer(*parentHandle);
1719-
1720-
XDCHECK(!parent.isInMMContainer());
1721-
XDCHECK(!parent.isAccessible());
1722-
1723-
// TODO: add multi-tier support (similar as for unchained items)
1724-
1725-
// We need to make sure the parent is not marked as moving
1726-
// and we're the only holder of the parent item. Safe to destroy the handle
1727-
// here since moving bit is set.
1728-
if (parentHandle->isMoving()) {
1729-
stats_.evictFailParentMove.inc();
1730-
return ItemHandle{};
1731-
}
1732-
1733-
if (evictToNvmCache && shouldWriteToNvmCacheExclusive(*parentHandle)) {
1734-
XDCHECK(token.isValid());
1735-
XDCHECK(parentHandle->hasChainedItem());
1736-
nvmCache_->put(parentHandle, std::move(token));
1737-
}
1738-
1739-
return parentHandle;
1620+
typename CacheAllocator<CacheTrait>::WriteHandle
1621+
CacheAllocator<CacheTrait>::tryEvictToNextMemoryTier(Item& item) {
1622+
auto tid = getTierId(item);
1623+
auto pid = allocator_[tid]->getAllocInfo(item.getMemory()).poolId;
1624+
return tryEvictToNextMemoryTier(tid, pid, item);
17401625
}
17411626

17421627
template <typename CacheTrait>
@@ -2936,7 +2821,7 @@ void CacheAllocator<CacheTrait>::evictForSlabRelease(
29362821
auto owningHandle =
29372822
item.isChainedItem()
29382823
? evictChainedItemForSlabRelease(item.asChainedItem())
2939-
: evictNormalItemForSlabRelease(item);
2824+
: evictNormalItem(item);
29402825

29412826
// we managed to evict the corresponding owner of the item and have the
29422827
// last handle for the owner.
@@ -2993,14 +2878,15 @@ void CacheAllocator<CacheTrait>::evictForSlabRelease(
29932878

29942879
template <typename CacheTrait>
29952880
typename CacheAllocator<CacheTrait>::ItemHandle
2996-
CacheAllocator<CacheTrait>::evictNormalItemForSlabRelease(Item& item) {
2881+
CacheAllocator<CacheTrait>::evictNormalItem(Item& item,
2882+
bool skipIfTokenInvalid) {
29972883
XDCHECK(item.isMoving());
29982884

29992885
if (item.isOnlyMoving()) {
30002886
return ItemHandle{};
30012887
}
30022888

3003-
auto evictHandle = tryEvictToNextMemoryTier(&item);
2889+
auto evictHandle = tryEvictToNextMemoryTier(item);
30042890
if(evictHandle) return evictHandle;
30052891

30062892
auto predicate = [](const Item& it) { return it.getRefCount() == 0; };
@@ -3009,6 +2895,11 @@ CacheAllocator<CacheTrait>::evictNormalItemForSlabRelease(Item& item) {
30092895
auto token = evictToNvmCache ? nvmCache_->createPutToken(item.getKey())
30102896
: typename NvmCacheT::PutToken{};
30112897

2898+
if (skipIfTokenInvalid && evictToNvmCache && !token.isValid()) {
2899+
stats_.evictFailConcurrentFill.inc();
2900+
return ItemHandle{};
2901+
}
2902+
30122903
// We remove the item from both access and mm containers. It doesn't matter
30132904
// if someone else calls remove on the item at this moment, the item cannot
30142905
// be freed as long as we have the moving bit set.

0 commit comments

Comments
 (0)