Skip to content

Commit

Permalink
Add combined locking support for MMContainer
Browse files Browse the repository at this point in the history
through withEvictionIterator function.

Also, expose config option to enable and disable
combined locking.

withEvictionIterator is implemented as en extra function,
getEvictionIterator() is still there and it's behavior hasn't
changed.
  • Loading branch information
igchor committed Dec 15, 2022
1 parent 227cac0 commit 0e9cbdf
Show file tree
Hide file tree
Showing 16 changed files with 359 additions and 93 deletions.
2 changes: 1 addition & 1 deletion cachelib/allocator/CacheAllocator.h
Original file line number Diff line number Diff line change
Expand Up @@ -1660,7 +1660,7 @@ class CacheAllocator : public CacheBase {
// @return An evicted item or nullptr if there is no suitable candidate.
Item* findEviction(PoolId pid, ClassId cid);

using EvictionIterator = typename MMContainer::Iterator;
using EvictionIterator = typename MMContainer::LockedIterator;

// Advance the current iterator and try to evict a regular item
//
Expand Down
41 changes: 17 additions & 24 deletions cachelib/allocator/MM2Q-inl.h
Original file line number Diff line number Diff line change
Expand Up @@ -241,29 +241,21 @@ bool MM2Q::Container<T, HookPtr>::add(T& node) noexcept {
}

template <typename T, MM2Q::Hook<T> T::*HookPtr>
typename MM2Q::Container<T, HookPtr>::Iterator
typename MM2Q::Container<T, HookPtr>::LockedIterator
MM2Q::Container<T, HookPtr>::getEvictionIterator() const noexcept {
// we cannot use combined critical sections with folly::DistributedMutex here
// because the lock is held for the lifetime of the eviction iterator. In
// other words, the abstraction of the iterator just does not lend itself well
// to combinable critical sections as the user can hold the lock for an
// arbitrary amount of time outside a lambda-friendly piece of code (eg. they
// can return the iterator from functions, pass it to functions, etc)
//
// it would be theoretically possible to refactor this interface into
// something like the following to allow combining
//
// mm2q.withEvictionIterator([&](auto iterator) {
// // user code
// });
//
// at the time of writing it is unclear if the gains from combining are
// reasonable justification for the codemod required to achieve combinability
// as we don't expect this critical section to be the hotspot in user code.
// This is however subject to change at some time in the future as and when
// this assertion becomes false.
LockHolder l(*lruMutex_);
return Iterator{std::move(l), lru_.rbegin()};
return LockedIterator{std::move(l), lru_.rbegin()};
}

template <typename T, MM2Q::Hook<T> T::*HookPtr>
template <typename F>
void MM2Q::Container<T, HookPtr>::withEvictionIterator(F&& fun) {
if (config_.useCombinedLockForIterators) {
lruMutex_->lock_combine([this, &fun]() { fun(Iterator{lru_.rbegin()}); });
} else {
LockHolder lck{*lruMutex_};
fun(Iterator{lru_.rbegin()});
}
}

template <typename T, MM2Q::Hook<T> T::*HookPtr>
Expand Down Expand Up @@ -462,8 +454,9 @@ void MM2Q::Container<T, HookPtr>::reconfigureLocked(const Time& currTime) {

// Iterator Context Implementation
template <typename T, MM2Q::Hook<T> T::*HookPtr>
MM2Q::Container<T, HookPtr>::Iterator::Iterator(
LockHolder l, const typename LruList::Iterator& iter) noexcept
: LruList::Iterator(iter), l_(std::move(l)) {}
MM2Q::Container<T, HookPtr>::LockedIterator::LockedIterator(
LockHolder l, const Iterator& iter) noexcept
: Iterator(iter), l_(std::move(l)) {}

} // namespace cachelib
} // namespace facebook
77 changes: 66 additions & 11 deletions cachelib/allocator/MM2Q.h
Original file line number Diff line number Diff line change
Expand Up @@ -214,6 +214,50 @@ class MM2Q {
size_t hotPercent,
size_t coldPercent,
uint32_t mmReconfigureInterval)
: Config(time,
ratio,
updateOnW,
updateOnR,
tryLockU,
rebalanceOnRecordAccs,
hotPercent,
coldPercent,
mmReconfigureInterval,
false) {}

// @param time the LRU refresh time.
// An item will be promoted only once in each
// lru refresh time depite the
// number of accesses it gets.
// @param ratio the LRU refresh ratio. The ratio times the
// oldest element's lifetime in warm queue
// would be the minimum value of LRU refresh
// time.
// @param udpateOnW whether to promote the item on write
// @param updateOnR whether to promote the item on read
// @param tryLockU whether to use a try lock when doing
// update.
// @param rebalanceOnRecordAccs whether to do rebalance on access. If set
// to false, rebalance only happens when
// items are added or removed to the queue.
// @param hotPercent percentage number for the size of the hot
// queue in the overall size.
// @param coldPercent percentage number for the size of the cold
// queue in the overall size.
// @param mmReconfigureInterval Time interval for recalculating lru
// refresh time according to the ratio.
// useCombinedLockForIterators Whether to use combined locking for
// withEvictionIterator
Config(uint32_t time,
double ratio,
bool updateOnW,
bool updateOnR,
bool tryLockU,
bool rebalanceOnRecordAccs,
size_t hotPercent,
size_t coldPercent,
uint32_t mmReconfigureInterval,
bool useCombinedLockForIterators)
: defaultLruRefreshTime(time),
lruRefreshRatio(ratio),
updateOnWrite(updateOnW),
Expand All @@ -223,7 +267,8 @@ class MM2Q {
hotSizePercent(hotPercent),
coldSizePercent(coldPercent),
mmReconfigureIntervalSecs(
std::chrono::seconds(mmReconfigureInterval)) {
std::chrono::seconds(mmReconfigureInterval)),
useCombinedLockForIterators(useCombinedLockForIterators) {
checkLruSizes();
}

Expand Down Expand Up @@ -306,6 +351,9 @@ class MM2Q {
// Minimum interval between reconfigurations. If 0, reconfigure is never
// called.
std::chrono::seconds mmReconfigureIntervalSecs{};

// Whether to use combined locking for withEvictionIterator.
bool useCombinedLockForIterators{false};
};

// The container object which can be used to keep track of objects of type
Expand Down Expand Up @@ -347,22 +395,24 @@ class MM2Q {
Container(const Container&) = delete;
Container& operator=(const Container&) = delete;

using Iterator = typename LruList::Iterator;

// context for iterating the MM container. At any given point of time,
// there can be only one iterator active since we need to lock the LRU for
// iteration. we can support multiple iterators at same time, by using a
// shared ptr in the context for the lock holder in the future.
class Iterator : public LruList::Iterator {
class LockedIterator : public Iterator {
public:
// noncopyable but movable.
Iterator(const Iterator&) = delete;
Iterator& operator=(const Iterator&) = delete;
LockedIterator(const LockedIterator&) = delete;
LockedIterator& operator=(const LockedIterator&) = delete;

Iterator(Iterator&&) noexcept = default;
LockedIterator(LockedIterator&&) noexcept = default;

// 1. Invalidate this iterator
// 2. Unlock
void destroy() {
LruList::Iterator::reset();
Iterator::reset();
if (l_.owns_lock()) {
l_.unlock();
}
Expand All @@ -373,15 +423,15 @@ class MM2Q {
if (!l_.owns_lock()) {
l_.lock();
}
LruList::Iterator::resetToBegin();
Iterator::resetToBegin();
}

private:
// private because it's easy to misuse and cause deadlock for MM2Q
Iterator& operator=(Iterator&&) noexcept = default;
LockedIterator& operator=(LockedIterator&&) noexcept = default;

// create an lru iterator with the lock being held.
Iterator(LockHolder l, const typename LruList::Iterator& iter) noexcept;
LockedIterator(LockHolder l, const Iterator& iter) noexcept;

// only the container can create iterators
friend Container<T, HookPtr>;
Expand Down Expand Up @@ -422,7 +472,7 @@ class MM2Q {

// same as the above but uses an iterator context. The iterator is updated
// on removal of the corresponding node to point to the next node. The
// iterator context holds the lock on the lru.
// iterator context is responsible for locking.
//
// iterator will be advanced to the next node after removing the node
//
Expand All @@ -445,7 +495,12 @@ class MM2Q {
// Obtain an iterator that start from the tail and can be used
// to search for evictions. This iterator holds a lock to this
// container and only one such iterator can exist at a time
Iterator getEvictionIterator() const noexcept;
LockedIterator getEvictionIterator() const noexcept;

// Execute provided function under container lock. Function gets
// Iterator passed as parameter.
template <typename F>
void withEvictionIterator(F&& f);

// get the current config as a copy
Config getConfig() const;
Expand Down
22 changes: 17 additions & 5 deletions cachelib/allocator/MMLru-inl.h
Original file line number Diff line number Diff line change
Expand Up @@ -212,10 +212,21 @@ bool MMLru::Container<T, HookPtr>::add(T& node) noexcept {
}

template <typename T, MMLru::Hook<T> T::*HookPtr>
typename MMLru::Container<T, HookPtr>::Iterator
typename MMLru::Container<T, HookPtr>::LockedIterator
MMLru::Container<T, HookPtr>::getEvictionIterator() const noexcept {
LockHolder l(*lruMutex_);
return Iterator{std::move(l), lru_.rbegin()};
return LockedIterator{std::move(l), lru_.rbegin()};
}

template <typename T, MMLru::Hook<T> T::*HookPtr>
template <typename F>
void MMLru::Container<T, HookPtr>::withEvictionIterator(F&& fun) {
if (config_.useCombinedLockForIterators) {
lruMutex_->lock_combine([this, &fun]() { fun(Iterator{lru_.rbegin()}); });
} else {
LockHolder lck{*lruMutex_};
fun(Iterator{lru_.rbegin()});
}
}

template <typename T, MMLru::Hook<T> T::*HookPtr>
Expand Down Expand Up @@ -360,8 +371,9 @@ void MMLru::Container<T, HookPtr>::reconfigureLocked(const Time& currTime) {

// Iterator Context Implementation
template <typename T, MMLru::Hook<T> T::*HookPtr>
MMLru::Container<T, HookPtr>::Iterator::Iterator(
LockHolder l, const typename LruList::Iterator& iter) noexcept
: LruList::Iterator(iter), l_(std::move(l)) {}
MMLru::Container<T, HookPtr>::LockedIterator::LockedIterator(
LockHolder l, const Iterator& iter) noexcept
: Iterator(iter), l_(std::move(l)) {}

} // namespace cachelib
} // namespace facebook
68 changes: 56 additions & 12 deletions cachelib/allocator/MMLru.h
Original file line number Diff line number Diff line change
Expand Up @@ -145,14 +145,49 @@ class MMLru {
bool tryLockU,
uint8_t ipSpec,
uint32_t mmReconfigureInterval)
: Config(time,
ratio,
updateOnW,
updateOnR,
tryLockU,
ipSpec,
mmReconfigureInterval,
false) {}

// @param time the LRU refresh time in seconds.
// An item will be promoted only once in each lru refresh
// time depite the number of accesses it gets.
// @param ratio the lru refresh ratio. The ratio times the
// oldest element's lifetime in warm queue
// would be the minimum value of LRU refresh time.
// @param udpateOnW whether to promote the item on write
// @param updateOnR whether to promote the item on read
// @param tryLockU whether to use a try lock when doing update.
// @param ipSpec insertion point spec, which is the inverse power of
// length from the end of the queue. For example, value 1
// means the insertion point is 1/2 from the end of LRU;
// value 2 means 1/4 from the end of LRU.
// @param mmReconfigureInterval Time interval for recalculating lru
// refresh time according to the ratio.
// useCombinedLockForIterators Whether to use combined locking for
// withEvictionIterator
Config(uint32_t time,
double ratio,
bool updateOnW,
bool updateOnR,
bool tryLockU,
uint8_t ipSpec,
uint32_t mmReconfigureInterval,
bool useCombinedLockForIterators)
: defaultLruRefreshTime(time),
lruRefreshRatio(ratio),
updateOnWrite(updateOnW),
updateOnRead(updateOnR),
tryLockUpdate(tryLockU),
lruInsertionPointSpec(ipSpec),
mmReconfigureIntervalSecs(
std::chrono::seconds(mmReconfigureInterval)) {}
std::chrono::seconds(mmReconfigureInterval)),
useCombinedLockForIterators(useCombinedLockForIterators) {}

Config() = default;
Config(const Config& rhs) = default;
Expand Down Expand Up @@ -198,6 +233,9 @@ class MMLru {
// Minimum interval between reconfigurations. If 0, reconfigure is never
// called.
std::chrono::seconds mmReconfigureIntervalSecs{};

// Whether to use combined locking for withEvictionIterator.
bool useCombinedLockForIterators{false};
};

// The container object which can be used to keep track of objects of type
Expand Down Expand Up @@ -234,22 +272,24 @@ class MMLru {
Container(const Container&) = delete;
Container& operator=(const Container&) = delete;

using Iterator = typename LruList::Iterator;

// context for iterating the MM container. At any given point of time,
// there can be only one iterator active since we need to lock the LRU for
// iteration. we can support multiple iterators at same time, by using a
// shared ptr in the context for the lock holder in the future.
class Iterator : public LruList::Iterator {
class LockedIterator : public Iterator {
public:
// noncopyable but movable.
Iterator(const Iterator&) = delete;
Iterator& operator=(const Iterator&) = delete;
LockedIterator(const LockedIterator&) = delete;
LockedIterator& operator=(const LockedIterator&) = delete;

Iterator(Iterator&&) noexcept = default;
LockedIterator(LockedIterator&&) noexcept = default;

// 1. Invalidate this iterator
// 2. Unlock
void destroy() {
LruList::Iterator::reset();
Iterator::reset();
if (l_.owns_lock()) {
l_.unlock();
}
Expand All @@ -260,15 +300,15 @@ class MMLru {
if (!l_.owns_lock()) {
l_.lock();
}
LruList::Iterator::resetToBegin();
Iterator::resetToBegin();
}

private:
// private because it's easy to misuse and cause deadlock for MMLru
Iterator& operator=(Iterator&&) noexcept = default;
LockedIterator& operator=(LockedIterator&&) noexcept = default;

// create an lru iterator with the lock being held.
Iterator(LockHolder l, const typename LruList::Iterator& iter) noexcept;
LockedIterator(LockHolder l, const Iterator& iter) noexcept;

// only the container can create iterators
friend Container<T, HookPtr>;
Expand Down Expand Up @@ -307,10 +347,9 @@ class MMLru {
// state of node is unchanged.
bool remove(T& node) noexcept;

using Iterator = Iterator;
// same as the above but uses an iterator context. The iterator is updated
// on removal of the corresponding node to point to the next node. The
// iterator context holds the lock on the lru.
// iterator context is responsible for locking.
//
// iterator will be advanced to the next node after removing the node
//
Expand All @@ -330,7 +369,12 @@ class MMLru {
// Obtain an iterator that start from the tail and can be used
// to search for evictions. This iterator holds a lock to this
// container and only one such iterator can exist at a time
Iterator getEvictionIterator() const noexcept;
LockedIterator getEvictionIterator() const noexcept;

// Execute provided function under container lock. Function gets
// iterator passed as parameter.
template <typename F>
void withEvictionIterator(F&& f);

// get copy of current config
Config getConfig() const;
Expand Down
Loading

0 comments on commit 0e9cbdf

Please sign in to comment.