forked from chromium/chromium
-
Notifications
You must be signed in to change notification settings - Fork 0
/
lock_free_address_hash_set.h
152 lines (128 loc) · 5.26 KB
/
lock_free_address_hash_set.h
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
// Copyright 2018 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef BASE_SAMPLING_HEAP_PROFILER_LOCK_FREE_ADDRESS_HASH_SET_H_
#define BASE_SAMPLING_HEAP_PROFILER_LOCK_FREE_ADDRESS_HASH_SET_H_
#include <cstdint>
#include <vector>
#include "base/atomicops.h"
#include "base/logging.h"
namespace base {
// A hash set container that provides lock-free versions of
// |Insert|, |Remove|, and |Contains| operations.
// It does not support concurrent write operations |Insert| and |Remove|
// over the same key. Concurrent writes of distinct keys are ok.
// |Contains| method can be executed concurrently with other |Insert|, |Remove|,
// or |Contains| even over the same key.
// However, please note the result of concurrent execution of |Contains|
// with |Insert| or |Remove| is racy.
//
// The hash set never rehashes, so the number of buckets stays the same
// for the lifetime of the set.
//
// Internally the hashset is implemented as a vector of N buckets
// (N has to be a power of 2). Each bucket holds a single-linked list of
// nodes each corresponding to a key.
// It is not possible to really delete nodes from the list as there might
// be concurrent reads being executed over the node. The |Remove| operation
// just marks the node as empty by placing nullptr into its key field.
// Consequent |Insert| operations may reuse empty nodes when possible.
//
// The structure of the hashset for N buckets is the following:
// 0: {*}--> {key1,*}--> {key2,*}--> NULL
// 1: {*}--> NULL
// 2: {*}--> {NULL,*}--> {key3,*}--> {key4,*}--> NULL
// ...
// N-1: {*}--> {keyM,*}--> NULL
class BASE_EXPORT LockFreeAddressHashSet {
public:
explicit LockFreeAddressHashSet(size_t buckets_count);
~LockFreeAddressHashSet();
// Checks if the |key| is in the set. Can be executed concurrently with
// |Insert|, |Remove|, and |Contains| operations.
bool Contains(void* key) const;
// Removes the |key| from the set. The key must be present in the set before
// the invocation.
// Can be concurrent with other |Insert| and |Remove| executions, provided
// they operate over distinct keys.
// Concurrent |Insert| or |Remove| executions over the same key are not
// supported.
void Remove(void* key);
// Inserts the |key| into the set. The key must not be present in the set
// before the invocation.
// Can be concurrent with other |Insert| and |Remove| executions, provided
// they operate over distinct keys.
// Concurrent |Insert| or |Remove| executions over the same key are not
// supported.
void Insert(void* key);
// Copies contents of |other| set into the current set. The current set
// must be empty before the call.
// The operation cannot be executed concurrently with any other methods.
void Copy(const LockFreeAddressHashSet& other);
size_t buckets_count() const { return buckets_.size(); }
size_t size() const {
return static_cast<size_t>(subtle::NoBarrier_Load(&size_));
}
// Returns the average bucket utilization.
float load_factor() const { return 1.f * size() / buckets_.size(); }
private:
friend class LockFreeAddressHashSetTest;
struct Node {
Node() : key(0), next(0) {}
explicit Node(void* key);
subtle::AtomicWord key;
subtle::AtomicWord next;
};
static uint32_t Hash(void* key);
Node* FindNode(void* key) const;
Node* Bucket(void* key) const;
static Node* next_node(Node* node) {
return reinterpret_cast<Node*>(subtle::NoBarrier_Load(&node->next));
}
std::vector<subtle::AtomicWord> buckets_;
size_t bucket_mask_;
subtle::AtomicWord size_ = 0;
};
inline LockFreeAddressHashSet::Node::Node(void* a_key) {
subtle::NoBarrier_Store(&key, reinterpret_cast<subtle::AtomicWord>(a_key));
subtle::NoBarrier_Store(&next, 0);
}
inline bool LockFreeAddressHashSet::Contains(void* key) const {
return FindNode(key) != nullptr;
}
inline void LockFreeAddressHashSet::Remove(void* key) {
Node* node = FindNode(key);
// TODO(alph): Replace with DCHECK.
CHECK(node != nullptr);
// We can never delete the node, nor detach it from the current bucket
// as there may always be another thread currently iterating over it.
// Instead we just mark it as empty, so |Insert| can reuse it later.
subtle::NoBarrier_Store(&node->key, 0);
subtle::NoBarrier_AtomicIncrement(&size_, -1);
}
inline LockFreeAddressHashSet::Node* LockFreeAddressHashSet::FindNode(
void* key) const {
for (Node* node = Bucket(key); node != nullptr; node = next_node(node)) {
void* k = reinterpret_cast<void*>(subtle::NoBarrier_Load(&node->key));
if (k == key)
return node;
}
return nullptr;
}
inline LockFreeAddressHashSet::Node* LockFreeAddressHashSet::Bucket(
void* key) const {
// TODO(alph): Replace with DCHECK.
CHECK(key != nullptr);
uint32_t h = Hash(key);
return reinterpret_cast<Node*>(
subtle::NoBarrier_Load(&buckets_[h & bucket_mask_]));
}
// static
inline uint32_t LockFreeAddressHashSet::Hash(void* key) {
// A simple fast hash function for addresses.
uint64_t k = static_cast<uint64_t>(reinterpret_cast<uintptr_t>(key));
uint64_t random_bits = 0x4bfdb9df5a6f243bull;
return static_cast<uint32_t>((k * random_bits) >> 32);
}
} // namespace base
#endif // BASE_SAMPLING_HEAP_PROFILER_LOCK_FREE_ADDRESS_HASH_SET_H_