cachelib/allocator/MMLru-inl.h (301 lines of code) (raw):

/* * Copyright (c) Facebook, Inc. and its affiliates. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ namespace facebook { namespace cachelib { namespace detail { template <typename T> bool areBytesSame(const T& one, const T& two) { return std::memcmp(&one, &two, sizeof(T)) == 0; } } // namespace detail /* Container Interface Implementation */ template <typename T, MMLru::Hook<T> T::*HookPtr> MMLru::Container<T, HookPtr>::Container(serialization::MMLruObject object, PtrCompressor compressor) : compressor_(std::move(compressor)), lru_(*object.lru(), compressor_), insertionPoint_(compressor_.unCompress( CompressedPtr{*object.compressedInsertionPoint()})), tailSize_(*object.tailSize()), config_(*object.config()) { lruRefreshTime_ = config_.lruRefreshTime; nextReconfigureTime_ = config_.mmReconfigureIntervalSecs.count() == 0 ? std::numeric_limits<Time>::max() : static_cast<Time>(util::getCurrentTimeSec()) + config_.mmReconfigureIntervalSecs.count(); } template <typename T, MMLru::Hook<T> T::*HookPtr> bool MMLru::Container<T, HookPtr>::recordAccess(T& node, AccessMode mode) noexcept { if ((mode == AccessMode::kWrite && !config_.updateOnWrite) || (mode == AccessMode::kRead && !config_.updateOnRead)) { return false; } const auto curr = static_cast<Time>(util::getCurrentTimeSec()); // check if the node is still being memory managed if (node.isInMMContainer() && ((curr >= getUpdateTime(node) + lruRefreshTime_.load(std::memory_order_relaxed)) || !isAccessed(node))) { if (!isAccessed(node)) { markAccessed(node); } auto func = [this, &node, curr]() { reconfigureLocked(curr); ensureNotInsertionPoint(node); if (node.isInMMContainer()) { lru_.moveToHead(node); setUpdateTime(node, curr); } if (isTail(node)) { unmarkTail(node); tailSize_--; XDCHECK_LE(0u, tailSize_); updateLruInsertionPoint(); } }; // if the tryLockUpdate optimization is on, and we were able to grab the // lock, execute the critical section and return true, else return false // // if the tryLockUpdate optimization is off, we always execute the // critical section and return true if (config_.tryLockUpdate) { if (auto lck = LockHolder{*lruMutex_, std::try_to_lock}) { func(); return true; } return false; } lruMutex_->lock_combine(func); return true; } return false; } template <typename T, MMLru::Hook<T> T::*HookPtr> cachelib::EvictionAgeStat MMLru::Container<T, HookPtr>::getEvictionAgeStat( uint64_t projectedLen) const noexcept { // we only need the projectedAge and warmQueueStat data items so we extract // those and return an EvictionAgeStat instance constructed from that data auto warmQueueStatAndAge = lruMutex_->lock_combine([this, projectedLen]() { auto stat = getEvictionAgeStatLocked(projectedLen); XDCHECK(detail::areBytesSame(stat.hotQueueStat, EvictionStatPerType{})); XDCHECK(detail::areBytesSame(stat.coldQueueStat, EvictionStatPerType{})); return std::make_pair(stat.warmQueueStat, stat.projectedAge); }); auto warmQueueStat = warmQueueStatAndAge.first; auto age = warmQueueStatAndAge.second; return EvictionAgeStat{warmQueueStat, {}, {}, age}; } template <typename T, MMLru::Hook<T> T::*HookPtr> cachelib::EvictionAgeStat MMLru::Container<T, HookPtr>::getEvictionAgeStatLocked( uint64_t projectedLength) const noexcept { EvictionAgeStat stat{}; const auto currTime = static_cast<Time>(util::getCurrentTimeSec()); const T* node = lru_.getTail(); stat.warmQueueStat.oldestElementAge = node ? currTime - getUpdateTime(*node) : 0; for (size_t numSeen = 0; numSeen < projectedLength && node != nullptr; numSeen++, node = lru_.getPrev(*node)) { } stat.projectedAge = node ? currTime - getUpdateTime(*node) : stat.warmQueueStat.oldestElementAge; return stat; } template <typename T, MMLru::Hook<T> T::*HookPtr> void MMLru::Container<T, HookPtr>::setConfig(const Config& newConfig) { lruMutex_->lock_combine([this, newConfig]() { config_ = newConfig; if (config_.lruInsertionPointSpec == 0 && insertionPoint_ != nullptr) { auto curr = insertionPoint_; while (tailSize_ != 0) { XDCHECK(curr != nullptr); unmarkTail(*curr); tailSize_--; curr = lru_.getNext(*curr); } insertionPoint_ = nullptr; } lruRefreshTime_.store(config_.lruRefreshTime, std::memory_order_relaxed); nextReconfigureTime_ = config_.mmReconfigureIntervalSecs.count() == 0 ? std::numeric_limits<Time>::max() : static_cast<Time>(util::getCurrentTimeSec()) + config_.mmReconfigureIntervalSecs.count(); }); } template <typename T, MMLru::Hook<T> T::*HookPtr> typename MMLru::Config MMLru::Container<T, HookPtr>::getConfig() const { return lruMutex_->lock_combine([this]() { return config_; }); } template <typename T, MMLru::Hook<T> T::*HookPtr> void MMLru::Container<T, HookPtr>::updateLruInsertionPoint() noexcept { if (config_.lruInsertionPointSpec == 0) { return; } // If insertionPoint_ is nullptr initialize it to tail first if (insertionPoint_ == nullptr) { insertionPoint_ = lru_.getTail(); tailSize_ = 0; if (insertionPoint_ != nullptr) { markTail(*insertionPoint_); tailSize_++; } } if (lru_.size() <= 1) { // we are done; return; } XDCHECK_NE(reinterpret_cast<uintptr_t>(nullptr), reinterpret_cast<uintptr_t>(insertionPoint_)); const auto expectedSize = lru_.size() >> config_.lruInsertionPointSpec; auto curr = insertionPoint_; while (tailSize_ < expectedSize && curr != lru_.getHead()) { curr = lru_.getPrev(*curr); markTail(*curr); tailSize_++; } while (tailSize_ > expectedSize && curr != lru_.getTail()) { unmarkTail(*curr); tailSize_--; curr = lru_.getNext(*curr); } insertionPoint_ = curr; } template <typename T, MMLru::Hook<T> T::*HookPtr> bool MMLru::Container<T, HookPtr>::add(T& node) noexcept { const auto currTime = static_cast<Time>(util::getCurrentTimeSec()); return lruMutex_->lock_combine([this, &node, currTime]() { if (node.isInMMContainer()) { return false; } if (config_.lruInsertionPointSpec == 0 || insertionPoint_ == nullptr) { lru_.linkAtHead(node); } else { lru_.insertBefore(*insertionPoint_, node); } node.markInMMContainer(); setUpdateTime(node, currTime); unmarkAccessed(node); updateLruInsertionPoint(); return true; }); } template <typename T, MMLru::Hook<T> T::*HookPtr> typename MMLru::Container<T, HookPtr>::Iterator MMLru::Container<T, HookPtr>::getEvictionIterator() const noexcept { LockHolder l(*lruMutex_); return Iterator{std::move(l), lru_.rbegin()}; } template <typename T, MMLru::Hook<T> T::*HookPtr> void MMLru::Container<T, HookPtr>::ensureNotInsertionPoint(T& node) noexcept { // If we are removing the insertion point node, grow tail before we remove // so that insertionPoint_ is valid (or nullptr) after removal if (&node == insertionPoint_) { insertionPoint_ = lru_.getPrev(*insertionPoint_); if (insertionPoint_ != nullptr) { tailSize_++; markTail(*insertionPoint_); } else { XDCHECK_EQ(lru_.size(), 1u); } } } template <typename T, MMLru::Hook<T> T::*HookPtr> void MMLru::Container<T, HookPtr>::removeLocked(T& node) { ensureNotInsertionPoint(node); lru_.remove(node); unmarkAccessed(node); if (isTail(node)) { unmarkTail(node); tailSize_--; } node.unmarkInMMContainer(); updateLruInsertionPoint(); return; } template <typename T, MMLru::Hook<T> T::*HookPtr> bool MMLru::Container<T, HookPtr>::remove(T& node) noexcept { return lruMutex_->lock_combine([this, &node]() { if (!node.isInMMContainer()) { return false; } removeLocked(node); return true; }); } template <typename T, MMLru::Hook<T> T::*HookPtr> void MMLru::Container<T, HookPtr>::remove(Iterator& it) noexcept { T& node = *it; XDCHECK(node.isInMMContainer()); ++it; removeLocked(node); } template <typename T, MMLru::Hook<T> T::*HookPtr> bool MMLru::Container<T, HookPtr>::replace(T& oldNode, T& newNode) noexcept { return lruMutex_->lock_combine([this, &oldNode, &newNode]() { if (!oldNode.isInMMContainer() || newNode.isInMMContainer()) { return false; } const auto updateTime = getUpdateTime(oldNode); lru_.replace(oldNode, newNode); oldNode.unmarkInMMContainer(); newNode.markInMMContainer(); setUpdateTime(newNode, updateTime); if (isAccessed(oldNode)) { markAccessed(newNode); } else { unmarkAccessed(newNode); } XDCHECK(!isTail(newNode)); if (isTail(oldNode)) { markTail(newNode); unmarkTail(oldNode); } else { unmarkTail(newNode); } if (insertionPoint_ == &oldNode) { insertionPoint_ = &newNode; } return true; }); } template <typename T, MMLru::Hook<T> T::*HookPtr> serialization::MMLruObject MMLru::Container<T, HookPtr>::saveState() const noexcept { serialization::MMLruConfig configObject; *configObject.lruRefreshTime() = lruRefreshTime_.load(std::memory_order_relaxed); *configObject.lruRefreshRatio() = config_.lruRefreshRatio; *configObject.updateOnWrite() = config_.updateOnWrite; *configObject.updateOnRead() = config_.updateOnRead; *configObject.tryLockUpdate() = config_.tryLockUpdate; *configObject.lruInsertionPointSpec() = config_.lruInsertionPointSpec; serialization::MMLruObject object; *object.config() = configObject; *object.compressedInsertionPoint() = compressor_.compress(insertionPoint_).saveState(); *object.tailSize() = tailSize_; *object.lru() = lru_.saveState(); return object; } template <typename T, MMLru::Hook<T> T::*HookPtr> MMContainerStat MMLru::Container<T, HookPtr>::getStats() const noexcept { auto stat = lruMutex_->lock_combine([this]() { auto* tail = lru_.getTail(); // we return by array here because DistributedMutex is fastest when the // output data fits within 48 bytes. And the array is exactly 48 bytes, so // it can get optimized by the implementation. // // the rest of the parameters are 0, so we don't need the critical section // to return them return folly::make_array(lru_.size(), tail == nullptr ? 0 : getUpdateTime(*tail), lruRefreshTime_.load(std::memory_order_relaxed)); }); return {stat[0] /* lru size */, stat[1] /* tail time */, stat[2] /* refresh time */, 0, 0, 0, 0}; } template <typename T, MMLru::Hook<T> T::*HookPtr> void MMLru::Container<T, HookPtr>::reconfigureLocked(const Time& currTime) { if (currTime < nextReconfigureTime_) { return; } nextReconfigureTime_ = currTime + config_.mmReconfigureIntervalSecs.count(); // update LRU refresh time auto stat = getEvictionAgeStatLocked(0); auto lruRefreshTime = std::min( std::max(config_.defaultLruRefreshTime, static_cast<uint32_t>(stat.warmQueueStat.oldestElementAge * config_.lruRefreshRatio)), kLruRefreshTimeCap); lruRefreshTime_.store(lruRefreshTime, std::memory_order_relaxed); } // 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)) {} } // namespace cachelib } // namespace facebook