AMDiS 2.10
The Adaptive Multi-Dimensional Simulation Toolbox
ConcurrentCache.hpp
1#pragma once
2
3#include <mutex>
4#include <shared_mutex>
5#include <thread>
6#include <tuple>
7#include <type_traits>
8#include <unordered_map>
9
10#include <dune/common/hash.hh>
11
12namespace AMDiS
13{
15 template <class Container>
16 struct ConsecutivePolicy;
17
19 template <class Container>
20 struct ThreadLocalPolicy;
21
23 template <class Container>
24 struct StaticLockedPolicy;
25
26
29
49 template <class Key,
50 class Data,
51 template <class> class Policy = ThreadLocalPolicy,
52 class Container = std::unordered_map<Key, Data>>
53 class ConcurrentCache;
54
55
56#ifdef DOXYGEN
58
66 template <class Container>
67 class ConcurrentCachePolicy;
68#endif
69
70 // implementation of the consecutive policy. Data is stored in instance variable.
71 template <class Container>
73 {
74 using key_type = typename Container::key_type;
75 using data_type = typename Container::mapped_type;
76 using container_type = Container;
77
78 template <class F, class... Args>
79 data_type const& get_or_init(key_type const& key, F&& f, Args&&... args) const
80 {
81 return impl(std::is_default_constructible<data_type>{}, key, FWD(f), FWD(args)...);
82 }
83
84 private:
85 // data_type is default_constructible
86 template <class F, class... Args>
87 data_type const& impl(std::true_type, key_type const& key, F&& f, Args&&... args) const
88 {
89 data_type empty;
90 auto it = cachedData_.emplace(key, std::move(empty));
91 if (it.second) {
92 data_type data = f(key, FWD(args)...);
93 it.first->second = std::move(data);
94 }
95 return it.first->second;
96 }
97
98 // data_type is not default_constructible
99 template <class F, class... Args>
100 data_type const& impl(std::false_type, key_type const& key, F&& f, Args&&... args) const
101 {
102 auto it = cachedData_.find(key);
103 if (it != cachedData_.end())
104 return it->second;
105 else {
106 data_type data = f(key, FWD(args)...);
107 auto it = cachedData_.emplace(key, std::move(data));
108 return it.first->second;
109 }
110 }
111
112 mutable container_type cachedData_;
113 };
114
115
116 // implementation of the ThreadLocal policy. Data is stored in thread_local variable.
117 template <class Container>
119 {
120 using key_type = typename Container::key_type;
121 using data_type = typename Container::mapped_type;
122 using container_type = Container;
123
124 template <class F, class... Args>
125 static data_type const& get_or_init(key_type const& key, F&& f, Args&&... args)
126 {
127 return impl(std::is_default_constructible<data_type>{}, key, FWD(f), FWD(args)...);
128 }
129
130 private:
131 // data_type is default_constructible
132 template <class F, class... Args>
133 static data_type const& impl(std::true_type, key_type const& key, F&& f, Args&&... args)
134 {
135 // Container to store the cached values
136 thread_local container_type cached_data;
137
138 data_type empty;
139 auto it = cached_data.emplace(key, std::move(empty));
140 if (it.second) {
141 data_type data = f(key, FWD(args)...);
142 it.first->second = std::move(data);
143 }
144 return it.first->second;
145 }
146
147 // data_type is not default_constructible
148 template <class F, class... Args>
149 static data_type const& impl(std::false_type, key_type const& key, F&& f, Args&&... args)
150 {
151 // Container to store the cached values
152 thread_local container_type cached_data;
153
154 auto it = cached_data.find(key);
155 if (it != cached_data.end())
156 return it->second;
157 else {
158 data_type data = f(key, FWD(args)...);
159 auto it = cached_data.emplace(key, std::move(data));
160 return it.first->second;
161 }
162 }
163 };
164
165
166 // implementation of the Shared policy. Data is stored in static variable.
167 template <class Container>
169 {
170 using key_type = typename Container::key_type;
171 using data_type = typename Container::mapped_type;
172 using container_type = Container;
173
174 template <class F, class... Args>
175 static data_type const& get_or_init(key_type const& key, F&& f, Args&&... args)
176 {
177 // Container to store the cached values
178 static container_type cached_data;
179
180 // mutex used to access the data in the container, necessary since
181 // access emplace is read-write.
182 using mutex_type = std::shared_timed_mutex;
183 static mutex_type access_mutex;
184
185 // first try to lock for read-only, if an element for key is found, return it,
186 // if not, obtain a unique_lock to insert a new element and initialize it.
187 std::shared_lock<mutex_type> read_lock(access_mutex);
188 auto it = cached_data.find(key);
189 if (it != cached_data.end())
190 return it->second;
191 else {
192 read_lock.unlock();
193 data_type data = f(key, FWD(args)...);
194 std::unique_lock<mutex_type> write_lock(access_mutex);
195 auto new_it = cached_data.emplace(key, std::move(data));
196 return new_it.first->second;
197 }
198 }
199 };
200
201
202 template <class Key, class Data, template <class> class Policy, class Container>
204 : protected Policy<Container>
205 {
206 using key_type = Key;
207 using data_type = Data;
208
209 public:
210
212
219 template <class F, class... Args>
220 data_type const& get(key_type const& key, F&& f, Args&&... args) const
221 {
222 static_assert(std::is_invocable_r_v<data_type, F, key_type, Args...>,
223 "Functor F must have the signature data_type(key_type, Args...)");
224
225 return ConcurrentCache::get_or_init(key, FWD(f), FWD(args)...);
226 }
227 };
228
229} // end namespace AMDiS
The class template ConcurrentCache describes an associative static container that allows the concurre...
Definition: ConcurrentCache.hpp:205
data_type const & get(key_type const &key, F &&f, Args &&... args) const
Return the data associated to the key.
Definition: ConcurrentCache.hpp:220
Store cache in instance.
Definition: ConcurrentCache.hpp:73
Stores cache global static, requires locking on write access.
Definition: ConcurrentCache.hpp:169
Store cache thread local, requires no locking.
Definition: ConcurrentCache.hpp:119