From 8efb072b9f1e0d41db1049437d8886940be71efc Mon Sep 17 00:00:00 2001 From: Chameleon Cloud User Date: Wed, 1 Apr 2026 18:31:37 +0000 Subject: [PATCH 1/2] base s3fifo without ghost --- cachelib/allocator/CMakeLists.txt | 1 + cachelib/allocator/CacheAllocator.h | 9 + .../allocator/CacheAllocatorS3FIFOCache.cpp | 21 + cachelib/allocator/CacheTraits.h | 8 + cachelib/allocator/ContainerTypes.cpp | 2 + cachelib/allocator/MMS3FIFO.h | 611 ++++++++++++++++++ cachelib/allocator/serialize/objects.thrift | 27 + cachelib/cachebench/cache/Cache.h | 21 + cachelib/cachebench/runner/Stressor.cpp | 12 + cachelib/cachebench/util/CacheConfig.cpp | 5 +- cachelib/cachebench/util/CacheConfig.h | 6 + 11 files changed, 722 insertions(+), 1 deletion(-) create mode 100644 cachelib/allocator/CacheAllocatorS3FIFOCache.cpp create mode 100644 cachelib/allocator/MMS3FIFO.h diff --git a/cachelib/allocator/CMakeLists.txt b/cachelib/allocator/CMakeLists.txt index aad79b0a52..ab8a4052d0 100644 --- a/cachelib/allocator/CMakeLists.txt +++ b/cachelib/allocator/CMakeLists.txt @@ -33,6 +33,7 @@ add_library (cachelib_allocator CacheAllocatorLru5BCacheWithSpinBuckets.cpp CacheAllocatorLruCache.cpp CacheAllocatorLruCacheWithSpinBuckets.cpp + CacheAllocatorS3FIFOCache.cpp CacheAllocatorTinyLFU5BCache.cpp CacheAllocatorTinyLFUCache.cpp CacheAllocatorWTinyLFU5BCache.cpp diff --git a/cachelib/allocator/CacheAllocator.h b/cachelib/allocator/CacheAllocator.h index 6082dee06d..807a23f511 100644 --- a/cachelib/allocator/CacheAllocator.h +++ b/cachelib/allocator/CacheAllocator.h @@ -6077,6 +6077,7 @@ namespace facebook::cachelib { extern template class CacheAllocator; extern template class CacheAllocator; extern template class CacheAllocator; +extern template class CacheAllocator; extern template class CacheAllocator; extern template class CacheAllocator; @@ -6098,6 +6099,14 @@ using LruAllocatorSpinBuckets = CacheAllocator; using Lru2QAllocator = CacheAllocator; using Lru5B2QAllocator = CacheAllocator; +// CacheAllocator with S3 FIFO eviction policy +// It maintains 2 queues, one for for probation and one for the main queue. +// New items are added to the probation queue, and if not accessed +// will be quickly removed from the cache to remove 1 hit wonders. +// If accessed while in probation, it will eventually be promoted to the main queue. +// Items in the tail of main queue will be reinserted if accessed. +using S3FIFOAllocator = CacheAllocator; + // CacheAllocator with Tiny LFU eviction policy // It has a window initially to gauage the frequency of accesses of newly // inserted items. And eventually it will onl admit items that are accessed diff --git a/cachelib/allocator/CacheAllocatorS3FIFOCache.cpp b/cachelib/allocator/CacheAllocatorS3FIFOCache.cpp new file mode 100644 index 0000000000..59e9431a99 --- /dev/null +++ b/cachelib/allocator/CacheAllocatorS3FIFOCache.cpp @@ -0,0 +1,21 @@ +/* + * Copyright (c) Meta Platforms, Inc. and 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. + */ + +#include "cachelib/allocator/CacheAllocator.h" + +namespace facebook::cachelib { +template class CacheAllocator; +} diff --git a/cachelib/allocator/CacheTraits.h b/cachelib/allocator/CacheTraits.h index 26605ae989..674d76e910 100644 --- a/cachelib/allocator/CacheTraits.h +++ b/cachelib/allocator/CacheTraits.h @@ -18,6 +18,7 @@ #include "cachelib/allocator/ChainedHashTable.h" #include "cachelib/allocator/MM2Q.h" #include "cachelib/allocator/MMLru.h" +#include "cachelib/allocator/MMS3FIFO.h" #include "cachelib/allocator/MMTinyLFU.h" #include "cachelib/allocator/MMWTinyLFU.h" #include "cachelib/allocator/memory/CompressedPtr.h" @@ -54,6 +55,13 @@ struct Lru2QCacheTrait { using CompressedPtrType = CompressedPtr4B; }; +struct S3FIFOCacheTrait { + using MMType = MMS3FIFO; + using AccessType = ChainedHashTable; + using AccessTypeLocks = SharedMutexBuckets; + using CompressedPtrType = CompressedPtr4B; +}; + struct TinyLFUCacheTrait { using MMType = MMTinyLFU; using AccessType = ChainedHashTable; diff --git a/cachelib/allocator/ContainerTypes.cpp b/cachelib/allocator/ContainerTypes.cpp index e70f051b84..55b62a0dae 100644 --- a/cachelib/allocator/ContainerTypes.cpp +++ b/cachelib/allocator/ContainerTypes.cpp @@ -17,6 +17,7 @@ #include "cachelib/allocator/ChainedHashTable.h" #include "cachelib/allocator/MM2Q.h" #include "cachelib/allocator/MMLru.h" +#include "cachelib/allocator/MMS3FIFO.h" #include "cachelib/allocator/MMTinyLFU.h" #include "cachelib/allocator/MMWTinyLFU.h" namespace facebook::cachelib { @@ -26,6 +27,7 @@ const int MMLru::kId = 1; const int MM2Q::kId = 2; const int MMTinyLFU::kId = 3; const int MMWTinyLFU::kId = 4; +const int MMS3FIFO::kId = 5; // AccessType const int ChainedHashTable::kId = 1; diff --git a/cachelib/allocator/MMS3FIFO.h b/cachelib/allocator/MMS3FIFO.h new file mode 100644 index 0000000000..3772c6e3a1 --- /dev/null +++ b/cachelib/allocator/MMS3FIFO.h @@ -0,0 +1,611 @@ +/* + * Copyright (c) Meta Platforms, Inc. and 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. + */ + +#pragma once + +#include + +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wconversion" +#include +#pragma GCC diagnostic pop + +#include +#include +#include + +#include "cachelib/allocator/Cache.h" +#include "cachelib/allocator/CacheStats.h" +#include "cachelib/allocator/Util.h" +#include "cachelib/allocator/datastruct/MultiDList.h" +#include "cachelib/allocator/memory/serialize/gen-cpp2/objects_types.h" +#include "cachelib/common/CompilerUtils.h" +#include "cachelib/common/Mutex.h" + +namespace facebook::cachelib { + +// S3-FIFO eviction policy with two FIFO queues: Small and Main. +// +// New items enter the Small queue. On access (recordAccess), items that are accessed +// are marked. +// +// Eviction follows the S3-FIFO paper: when Small exceeds `smallSizePercent` +// of total size, the Small tail is processed — accessed items are promoted +// to Main, unaccessed items become eviction victims. When Small is within +// target, eviction comes from Main's tail, after item in main's tail are +// reinserted to head of main if they are accessed. +class MMS3FIFO { + public: + // unique identifier per MMType + static const int kId; + + template + using Hook = DListHook; + using SerializationType = serialization::MMS3FIFOObject; + using SerializationConfigType = serialization::MMS3FIFOConfig; + using SerializationTypeContainer = serialization::MMS3FIFOCollection; + + // Main=0, Small=1: MultiDList rbegin() starts at highest index (Small) + // tail first, then Main tail — giving correct S3-FIFO eviction order. + enum LruType { Main, Small, NumTypes }; + + struct Config { + explicit Config(SerializationConfigType configState) + : Config(*configState.lruRefreshTime(), + *configState.lruRefreshRatio(), + *configState.updateOnWrite(), + *configState.updateOnRead(), + *configState.tryLockUpdate(), + *configState.smallSizePercent(), + *configState.ghostSizePercent()) {} + + Config(bool updateOnR, size_t smallSizePct, size_t ghostSizePct) + : Config(/* time */ 60, + /* ratio */ 0.0, + /* updateOnW */ false, + updateOnR, + /* tryLockU */ false, + smallSizePct, + ghostSizePct, + /* mmReconfigureInterval */ 0, + /* useCombinedLockForIterators */ false) {} + + Config(uint32_t time, + double ratio, + bool updateOnW, + bool updateOnR, + bool tryLockU, + size_t smallSizePct, + size_t ghostSizePct) + : Config(time, + ratio, + updateOnW, + updateOnR, + tryLockU, + smallSizePct, + ghostSizePct, + /* mmReconfigureInterval */ 0, + /* useCombinedLockForIterators */ false) {} + + Config(uint32_t time, + double ratio, + bool updateOnW, + bool updateOnR, + bool tryLockU, + size_t smallSizePct, + size_t ghostSizePct, + uint32_t mmReconfigureInterval, + bool useCombinedLockForIterators) + : defaultLruRefreshTime(time), + lruRefreshRatio(ratio), + updateOnWrite(updateOnW), + updateOnRead(updateOnR), + tryLockUpdate(tryLockU), + smallSizePercent(smallSizePct), + ghostSizePercent(ghostSizePct), + mmReconfigureIntervalSecs( + std::chrono::seconds(mmReconfigureInterval)), + useCombinedLockForIterators(useCombinedLockForIterators) { + checkConfig(); + } + + Config() = default; + Config(const Config& rhs) = default; + Config(Config&& rhs) = default; + + Config& operator=(const Config& rhs) = default; + Config& operator=(Config&& rhs) = default; + + void checkConfig() { + if (smallSizePercent < 1 || smallSizePercent > 50) { + throw std::invalid_argument( + folly::sformat("Invalid small queue size {}. Small queue size " + "must be between 1% and 50% of total cache size.", + smallSizePercent)); + } + } + + template + void addExtraConfig(Args...) {} + + uint32_t defaultLruRefreshTime{60}; + uint32_t lruRefreshTime{defaultLruRefreshTime}; + + double lruRefreshRatio{0.}; + + bool updateOnWrite{false}; + + bool updateOnRead{true}; + + bool tryLockUpdate{false}; + + // The size of the Small queue as a percentage of the total size. + size_t smallSizePercent{10}; + + // Reserved for ghost queue sizing. Stored and serialized even though the + // ghost queue itself is not wired into eviction behavior yet. + size_t ghostSizePercent{100}; + + std::chrono::seconds mmReconfigureIntervalSecs{}; + + bool useCombinedLockForIterators{false}; + }; + + template T::* HookPtr> + struct Container { + private: + using LruList = MultiDList; + using Mutex = folly::DistributedMutex; + using LockHolder = std::unique_lock; + using PtrCompressor = typename T::PtrCompressor; + using Time = typename Hook::Time; + using CompressedPtrType = typename T::CompressedPtrType; + using RefFlags = typename T::Flags; + + public: + Container() = default; + Container(Config c, PtrCompressor compressor) + : lru_(LruType::NumTypes, std::move(compressor)), + config_(std::move(c)) {} + Container(serialization::MMS3FIFOObject object, PtrCompressor compressor); + + Container(const Container&) = delete; + Container& operator=(const Container&) = delete; + + using Iterator = typename LruList::Iterator; + + class LockedIterator : public Iterator { + public: + LockedIterator(const LockedIterator&) = delete; + LockedIterator& operator=(const LockedIterator&) = delete; + + LockedIterator(LockedIterator&&) noexcept = default; + + void destroy() { + Iterator::reset(); + if (l_.owns_lock()) { + l_.unlock(); + } + } + + void resetToBegin() { + if (!l_.owns_lock()) { + l_.lock(); + } + Iterator::resetToBegin(); + } + + private: + LockedIterator& operator=(LockedIterator&&) noexcept = default; + + LockedIterator(LockHolder l, const Iterator& iter) noexcept; + + friend Container; + + LockHolder l_; + }; + + // In S3-FIFO, recordAccess lazily marks Small items as accessed + // (no lock, no list ops). Promotion to Main is deferred to eviction + // time. Main is pure FIFO — recordAccess is a no-op. + bool recordAccess(T& node, AccessMode mode) noexcept; + + // Adds the node to the Small queue head. + bool add(T& node) noexcept; + + bool remove(T& node) noexcept; + + void remove(Iterator& it) noexcept; + + bool replace(T& oldNode, T& newNode) noexcept; + + LockedIterator getEvictionIterator() const noexcept; + + template + void withEvictionIterator(F&& f); + + template + void withContainerLock(F&& f); + + Config getConfig() const; + + void setConfig(const Config& newConfig); + + bool isEmpty() const noexcept { return size() == 0; } + + size_t size() const noexcept { + return lruMutex_->lock_combine([this]() { return lru_.size(); }); + } + + EvictionAgeStat getEvictionAgeStat(uint64_t projectedLength) const noexcept; + + serialization::MMS3FIFOObject saveState() const noexcept; + + MMContainerStat getStats() const noexcept; + + static LruType getLruType(const T& node) noexcept { + return isSmall(node) ? LruType::Small : LruType::Main; + } + + private: + EvictionAgeStat getEvictionAgeStatLocked( + uint64_t projectedLength) const noexcept; + + static Time getUpdateTime(const T& node) noexcept { + return (node.*HookPtr).getUpdateTime(); + } + + static void setUpdateTime(T& node, Time time) noexcept { + (node.*HookPtr).setUpdateTime(time); + } + + void removeLocked(T& node) noexcept; + + // Lazy promotion: when Small exceeds smallSizePercent, scan Small tail + // and promote accessed items to Main. Called under lock before yielding + // the eviction iterator. Const-safe because lru_ is mutable. + void lazyPromoteSmallTailLocked() const noexcept; + + void reinsertMain() const noexcept; + + // Flag helpers: kMMFlag0 = "in Small queue" + static bool isSmall(const T& node) noexcept { + return node.template isFlagSet(); + } + static void markSmall(T& node) noexcept { + node.template setFlag(); + } + static void unmarkSmall(T& node) noexcept { + node.template unSetFlag(); + } + + // Flag helpers: kMMFlag1 = "accessed" (for stats) + static bool isAccessed(const T& node) noexcept { + return node.template isFlagSet(); + } + static void markAccessed(T& node) noexcept { + node.template setFlag(); + } + static void unmarkAccessed(T& node) noexcept { + node.template unSetFlag(); + } + + mutable folly::cacheline_aligned lruMutex_; + + mutable LruList lru_{}; + + Config config_{}; + + friend class MMTypeTest; + }; +}; + +/* Container Interface Implementation */ +template T::* HookPtr> +MMS3FIFO::Container::Container(serialization::MMS3FIFOObject object, + PtrCompressor compressor) + : lru_(*object.lrus(), std::move(compressor)), config_(*object.config()) {} + +template T::* HookPtr> +bool MMS3FIFO::Container::recordAccess(T& node, + AccessMode mode) noexcept { + if ((mode == AccessMode::kWrite && !config_.updateOnWrite) || + (mode == AccessMode::kRead && !config_.updateOnRead)) { + return false; + } + + const auto curr = static_cast