Skip to content

Commit 0566f6a

Browse files
concurrent hash map for ghost
1 parent d048750 commit 0566f6a

File tree

7 files changed

+170
-69
lines changed

7 files changed

+170
-69
lines changed

cachelib/allocator/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@ add_library (cachelib_allocator
3434
CacheAllocatorLruCache.cpp
3535
CacheAllocatorLruCacheWithSpinBuckets.cpp
3636
CacheAllocatorS3FIFOCache.cpp
37+
CacheAllocatorS3FIFO5BCache.cpp
3738
CacheAllocatorTinyLFU5BCache.cpp
3839
CacheAllocatorTinyLFUCache.cpp
3940
CacheAllocatorWTinyLFU5BCache.cpp

cachelib/allocator/CacheAllocator.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6106,6 +6106,7 @@ using Lru5B2QAllocator = CacheAllocator<Lru5B2QCacheTrait>;
61066106
// If accessed while in probation, it will eventually be promoted to the main queue.
61076107
// Items in the tail of main queue will be reinserted if accessed.
61086108
using S3FIFOAllocator = CacheAllocator<S3FIFOCacheTrait>;
6109+
using S3FIFO5BAllocator = CacheAllocator<S3FIFO5BCacheTrait>;
61096110

61106111
// CacheAllocator with Tiny LFU eviction policy
61116112
// It has a window initially to gauage the frequency of accesses of newly
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
/*
2+
* Copyright (c) Meta Platforms, Inc. and affiliates.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
#include "cachelib/allocator/CacheAllocator.h"
18+
19+
namespace facebook::cachelib {
20+
template class CacheAllocator<S3FIFO5BCacheTrait>;
21+
}

cachelib/allocator/CacheTraits.h

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -97,6 +97,13 @@ struct Lru5B2QCacheTrait {
9797
using CompressedPtrType = CompressedPtr5B;
9898
};
9999

100+
struct S3FIFO5BCacheTrait {
101+
using MMType = MMS3FIFO;
102+
using AccessType = ChainedHashTable;
103+
using AccessTypeLocks = SharedMutexBuckets;
104+
using CompressedPtrType = CompressedPtr5B;
105+
};
106+
100107
struct TinyLFU5BCacheTrait {
101108
using MMType = MMTinyLFU;
102109
using AccessType = ChainedHashTable;

cachelib/allocator/MMS3FIFO.h

Lines changed: 48 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@
3333
#include "cachelib/allocator/memory/serialize/gen-cpp2/objects_types.h"
3434
#include "cachelib/common/CompilerUtils.h"
3535
#include "cachelib/common/FIFOHashSet.h"
36+
#include "cachelib/common/FIFOConcurrentHashSet.h"
3637

3738
namespace facebook::cachelib {
3839

@@ -112,8 +113,6 @@ class MMS3FIFO {
112113

113114
// The size of ghost queue, as a percentage of total size
114115
size_t ghostSizePercent{90};
115-
116-
size_t reserveCapacity{0};
117116
};
118117

119118
// The container object which can be used to keep track of objects of type
@@ -138,9 +137,6 @@ class MMS3FIFO {
138137
Container(Config c, PtrCompressor compressor)
139138
: lru_(LruType::NumTypes, std::move(compressor)),
140139
config_(std::move(c)) {
141-
if (config_.reserveCapacity > 0) {
142-
ghostQueue_.reserve(config_.reserveCapacity);
143-
}
144140
}
145141
Container(serialization::MMS3FIFOObject object, PtrCompressor compressor);
146142

@@ -216,7 +212,7 @@ class MMS3FIFO {
216212
++it;
217213
// Skip accessed items
218214
skipAccessed(it);
219-
return *this;
215+
return *this;
220216
}
221217

222218
ListIterator& skipAccessed(ListIterator& it) noexcept {
@@ -389,10 +385,15 @@ class MMS3FIFO {
389385
void maybeResizeGhostLocked() noexcept;
390386

391387
// Returns the hash of node's key
392-
static size_t hashNode(const T& node) noexcept {
388+
static size_t hashNode64(const T& node) noexcept {
393389
return folly::hasher<folly::StringPiece>()(node.getKey());
394390
}
395391

392+
static uint32_t hashNode(const T& node) noexcept {
393+
return static_cast<uint32_t>(
394+
folly::hasher<folly::StringPiece>()(node.getKey()));
395+
}
396+
396397
void removeLocked(T& node) noexcept;
397398

398399
static bool isTiny(const T& node) noexcept {
@@ -432,7 +433,8 @@ class MMS3FIFO {
432433
// the lru
433434
LruList lru_;
434435

435-
facebook::cachelib::util::FIFOHashSet ghostQueue_;
436+
facebook::cachelib::util::FIFOConcurrentHashSet32 ghostQueue_;
437+
// facebook::cachelib::util::FIFOHashSet32 ghostQueue_;
436438

437439
// Config for this lru.
438440
// Write access to the MMS3FIFO Config is serialized.
@@ -483,7 +485,7 @@ bool MMS3FIFO::Container<T, HookPtr>::recordAccess(T& node,
483485
}
484486
const auto currTime = static_cast<Time>(util::getCurrentTimeSec());
485487

486-
// Remove lock from record access wince we only set bits
488+
// Remove lock from record access since we only set bits
487489
if (node.isInMMContainer()) {
488490
if (!isAccessed(node)) {
489491
markAccessed(node);
@@ -538,12 +540,12 @@ bool MMS3FIFO::Container<T, HookPtr>::add(T& node) noexcept {
538540
const auto currTime = static_cast<Time>(util::getCurrentTimeSec());
539541

540542
const auto nodeHash = hashNode(node);
541-
return lruMutex_->lock_combine([this, &node, currTime, nodeHash]() {
543+
const auto ghostContains = ghostQueue_.contains(nodeHash);
544+
return lruMutex_->lock_combine([this, &node, currTime, ghostContains]() {
542545
if (node.isInMMContainer()) {
543546
return false;
544547
}
545548

546-
const auto ghostContains = ghostQueue_.contains(nodeHash);
547549
if (ghostContains) {
548550
// Insert to main queue
549551
auto& mainLru = lru_.getList(LruType::Main);
@@ -625,6 +627,7 @@ template <typename T, MMS3FIFO::Hook<T> T::* HookPtr>
625627
typename MMS3FIFO::Container<T, HookPtr>::LockedIterator
626628
MMS3FIFO::Container<T, HookPtr>::getEvictionIterator() noexcept {
627629
LockHolder l(*lruMutex_);
630+
// This is cheap
628631
maybeResizeGhostLocked();
629632
rebalanceForEviction();
630633
// Cache is full now so we know it's max size
@@ -643,13 +646,12 @@ void MMS3FIFO::Container<T, HookPtr>::withContainerLock(F&& fun) {
643646
lruMutex_->lock_combine([&fun]() { fun(); });
644647
}
645648

649+
// Ghost queue insertion done on callee, outside lock
646650
template <typename T, MMS3FIFO::Hook<T> T::* HookPtr>
647651
void MMS3FIFO::Container<T, HookPtr>::removeLocked(T& node) noexcept {
648652
if (isTiny(node)) {
649653
lru_.getList(LruType::Tiny).remove(node);
650654
unmarkTiny(node);
651-
// Insert into ghost queue upon eviction from tiny queue
652-
ghostQueue_.insert(hashNode(node));
653655
} else {
654656
lru_.getList(LruType::Main).remove(node);
655657
}
@@ -661,21 +663,51 @@ void MMS3FIFO::Container<T, HookPtr>::removeLocked(T& node) noexcept {
661663

662664
template <typename T, MMS3FIFO::Hook<T> T::* HookPtr>
663665
bool MMS3FIFO::Container<T, HookPtr>::remove(T& node) noexcept {
664-
return lruMutex_->lock_combine([this, &node]() {
666+
bool isTiny_ = isTiny(node);
667+
auto result = lruMutex_->lock_combine([this, &node]() {
665668
if (!node.isInMMContainer()) {
666669
return false;
667670
}
668671
removeLocked(node);
669672
return true;
670673
});
674+
if (result && isTiny_) {
675+
// Insert to ghost queue
676+
ghostQueue_.insert(hashNode(node));
677+
}
678+
return result;
671679
}
672680

673681
template <typename T, MMS3FIFO::Hook<T> T::* HookPtr>
674682
void MMS3FIFO::Container<T, HookPtr>::remove(LockedIterator& it) noexcept {
675683
T& node = *it;
676684
XDCHECK(node.isInMMContainer());
677-
++it;
678-
removeLocked(node);
685+
++it;
686+
687+
bool evictedFromTiny = false;
688+
if (isTiny(node)) {
689+
lru_.getList(LruType::Tiny).remove(node);
690+
unmarkTiny(node);
691+
evictedFromTiny = true;
692+
// Insert into ghost queue upon eviction from tiny queue
693+
} else {
694+
lru_.getList(LruType::Main).remove(node);
695+
}
696+
697+
unmarkAccessed(node);
698+
node.unmarkInMMContainer();
699+
700+
if (evictedFromTiny) {
701+
// We need to insert to ghost queue.
702+
// Release lock early
703+
// AFAIK every call to remove(locked itr) is followed by the iterator's destruction.
704+
// If this is not the case, we may need to rethink this.
705+
if (it.l_.owns_lock()) {
706+
it.l_.unlock();
707+
}
708+
ghostQueue_.insert(hashNode(node));
709+
}
710+
return;
679711
}
680712

681713
template <typename T, MMS3FIFO::Hook<T> T::* HookPtr>
Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
2+
#pragma once
3+
#include <folly/concurrency/ConcurrentHashMap.h>
4+
5+
#include <atomic>
6+
#include <deque>
7+
#include <mutex>
8+
9+
namespace facebook::cachelib::util {
10+
namespace detail {
11+
12+
template <typename KeyT = uint32_t>
13+
class FIFOConcurrentHashSetBase {
14+
public:
15+
using Key = KeyT; // hashed object id
16+
using TS = uint64_t; // logical time / FIFO order
17+
18+
explicit FIFOConcurrentHashSetBase(size_t capacity)
19+
: ghost_(capacity), seq_(0), queueSize_(0) {}
20+
21+
FIFOConcurrentHashSetBase() : seq_(0), queueSize_(0) {}
22+
23+
struct PendingEntry {
24+
Key k;
25+
TS ts;
26+
};
27+
28+
inline void insert(Key k) {
29+
TS ts = seq_.fetch_add(1, std::memory_order_relaxed);
30+
ghost_.insert_or_assign(k, ts);
31+
// If it overflows and wraps around, we reset the entire structure.
32+
if (UNLIKELY(ts == std::numeric_limits<TS>::max())) {
33+
ghost_.clear();
34+
}
35+
}
36+
37+
inline bool contains(Key k) {
38+
auto it = ghost_.find(k);
39+
if (it == ghost_.end())
40+
return false;
41+
// Seq - current must be larger
42+
auto currDiff = seq_.load(std::memory_order_relaxed) - it->second;
43+
auto isValid = currDiff <= queueSize_.load(std::memory_order_relaxed);
44+
45+
if (!isValid) {
46+
// remove stale entry
47+
ghost_.erase(k);
48+
}
49+
50+
return isValid;
51+
}
52+
53+
inline void resize(TS limitTS) {
54+
queueSize_.store(limitTS, std::memory_order_relaxed);
55+
}
56+
57+
private:
58+
folly::ConcurrentHashMap<Key, TS> ghost_;
59+
60+
// Logical TS counter
61+
std::atomic<TS> seq_;
62+
// Maximum size of the FIFO queue
63+
std::atomic<TS> queueSize_;
64+
};
65+
66+
} // namespace detail
67+
using FIFOConcurrentHashSet32 = detail::FIFOConcurrentHashSetBase<uint32_t>;
68+
using FIFOConcurrentHashSet64 = detail::FIFOConcurrentHashSetBase<uint64_t>;
69+
70+
} // namespace facebook::cachelib::util

0 commit comments

Comments
 (0)