@@ -584,7 +584,8 @@ using llvm::hash_value;
584
584
// / process. It has no destructor, to avoid generating useless global destructor
585
585
// / calls. The memory it allocates can be freed by calling clear() with no
586
586
// / outstanding readers, but this won't destroy the static mutex it uses.
587
- template <class ElemTy > struct ConcurrentReadableHashMap {
587
+ template <class ElemTy , class MutexTy = StaticMutex>
588
+ struct ConcurrentReadableHashMap {
588
589
// We use memcpy and don't call destructors. Make sure the elements will put
589
590
// up with this.
590
591
static_assert (std::is_trivially_copyable<ElemTy>::value,
@@ -593,6 +594,9 @@ template <class ElemTy> struct ConcurrentReadableHashMap {
593
594
" Elements must not have destructors (they won't be called)." );
594
595
595
596
private:
597
+ // A scoped lock type to use on MutexTy.
598
+ using ScopedLockTy = ScopedLockT<MutexTy, false >;
599
+
596
600
// / The reciprocal of the load factor at which we expand the table. A value of
597
601
// / 4 means that we resize at 1/4 = 75% load factor.
598
602
static const size_t ResizeProportion = 4 ;
@@ -752,7 +756,7 @@ template <class ElemTy> struct ConcurrentReadableHashMap {
752
756
std::atomic<IndexStorage *> Indices{nullptr };
753
757
754
758
// / The writer lock, which must be taken before any mutation of the table.
755
- StaticMutex WriterLock;
759
+ MutexTy WriterLock;
756
760
757
761
// / The list of pointers to be freed once no readers are active.
758
762
FreeListNode *FreeList{nullptr };
@@ -966,7 +970,7 @@ template <class ElemTy> struct ConcurrentReadableHashMap {
966
970
// / The return value is ignored when `created` is `false`.
967
971
template <class KeyTy , typename Call>
968
972
void getOrInsert (KeyTy key, const Call &call) {
969
- StaticScopedLock guard (WriterLock);
973
+ ScopedLockTy guard (WriterLock);
970
974
971
975
auto *indices = Indices.load (std::memory_order_relaxed);
972
976
if (!indices)
@@ -1018,7 +1022,7 @@ template <class ElemTy> struct ConcurrentReadableHashMap {
1018
1022
// / Clear the hash table, freeing (when safe) all memory currently used for
1019
1023
// / indices and elements.
1020
1024
void clear () {
1021
- StaticScopedLock guard (WriterLock);
1025
+ ScopedLockTy guard (WriterLock);
1022
1026
1023
1027
auto *indices = Indices.load (std::memory_order_relaxed);
1024
1028
auto *elements = Elements.load (std::memory_order_relaxed);
@@ -1054,28 +1058,38 @@ template <class ElemTy> struct HashMapElementWrapper {
1054
1058
// / by allocating them separately and storing pointers to them. The elements of
1055
1059
// / the hash table are instances of HashMapElementWrapper. A new getOrInsert
1056
1060
// / method is provided that directly returns the stable element pointer.
1057
- template <class ElemTy , class Allocator >
1061
+ template <class ElemTy , class Allocator , class MutexTy = StaticMutex >
1058
1062
struct StableAddressConcurrentReadableHashMap
1059
- : public ConcurrentReadableHashMap<HashMapElementWrapper<ElemTy>> {
1063
+ : public ConcurrentReadableHashMap<HashMapElementWrapper<ElemTy>, MutexTy > {
1060
1064
// Implicitly trivial destructor.
1061
1065
~StableAddressConcurrentReadableHashMap () = default ;
1062
1066
1067
+ std::atomic<ElemTy *> LastFound{nullptr };
1068
+
1063
1069
// / Get or insert an element for the given key and arguments. Returns the
1064
1070
// / pointer to the existing or new element, and a bool indicating whether the
1065
1071
// / element was created. When false, the element already existed before the
1066
1072
// / call.
1067
1073
template <class KeyTy , class ... ArgTys>
1068
1074
std::pair<ElemTy *, bool > getOrInsert (KeyTy key, ArgTys &&...args) {
1075
+ // See if this is a repeat of the previous search.
1076
+ if (auto lastFound = LastFound.load (std::memory_order_acquire))
1077
+ if (lastFound->matchesKey (key))
1078
+ return {lastFound, false };
1079
+
1069
1080
// Optimize for the case where the value already exists.
1070
- if (auto wrapper = this ->snapshot ().find (key))
1081
+ if (auto wrapper = this ->snapshot ().find (key)) {
1082
+ LastFound.store (wrapper->Ptr , std::memory_order_relaxed);
1071
1083
return {wrapper->Ptr , false };
1084
+ }
1072
1085
1073
1086
// No such element. Insert if needed. Note: another thread may have inserted
1074
1087
// it in the meantime, so we still have to handle both cases!
1075
1088
ElemTy *ptr = nullptr ;
1076
1089
bool outerCreated = false ;
1077
- ConcurrentReadableHashMap<HashMapElementWrapper<ElemTy>>::getOrInsert (
1078
- key, [&](HashMapElementWrapper<ElemTy> *wrapper, bool created) {
1090
+ ConcurrentReadableHashMap<HashMapElementWrapper<ElemTy>, MutexTy>::
1091
+ getOrInsert (key, [&](HashMapElementWrapper<ElemTy> *wrapper,
1092
+ bool created) {
1079
1093
if (created) {
1080
1094
// Created the indirect entry. Allocate the actual storage.
1081
1095
size_t allocSize =
@@ -1088,9 +1102,17 @@ struct StableAddressConcurrentReadableHashMap
1088
1102
outerCreated = created;
1089
1103
return true ; // Keep the new entry.
1090
1104
});
1105
+ LastFound.store (ptr, std::memory_order_relaxed);
1091
1106
return {ptr, outerCreated};
1092
1107
}
1093
1108
1109
+ template <class KeyTy > ElemTy *find (const KeyTy &key) {
1110
+ auto result = this ->snapshot ().find (key);
1111
+ if (!result)
1112
+ return nullptr ;
1113
+ return result->Ptr ;
1114
+ }
1115
+
1094
1116
private:
1095
1117
// Clearing would require deallocating elements, which we don't support.
1096
1118
void clear () = delete;
0 commit comments