Reference documentation for deal.II version GIT relicensing-487-ge9eb5ab491 2024-04-25 07:20:02+00:00
\(\newcommand{\dealvcentcolon}{\mathrel{\mathop{:}}}\) \(\newcommand{\dealcoloneq}{\dealvcentcolon\mathrel{\mkern-1.2mu}=}\) \(\newcommand{\jump}[1]{\left[\!\left[ #1 \right]\!\right]}\) \(\newcommand{\average}[1]{\left\{\!\left\{ #1 \right\}\!\right\}}\)
Loading...
Searching...
No Matches
lazy.h
Go to the documentation of this file.
1// ------------------------------------------------------------------------
2//
3// SPDX-License-Identifier: LGPL-2.1-or-later
4// Copyright (C) 2023 - 2024 by the deal.II authors
5//
6// This file is part of the deal.II library.
7//
8// Part of the source code is dual licensed under Apache-2.0 WITH
9// LLVM-exception OR LGPL-2.1-or-later. Detailed license information
10// governing the source code and code contributions can be found in
11// LICENSE.md and CONTRIBUTING.md at the top level directory of deal.II.
12//
13// ------------------------------------------------------------------------
14
15#ifndef dealii_lazy_h
16#define dealii_lazy_h
17
18
19#include <deal.II/base/config.h>
20
23#include <deal.II/base/mutex.h>
24
25#include <atomic>
26#include <mutex>
27#include <optional>
28#include <type_traits>
29
30
32
82template <typename T>
83DEAL_II_CXX20_REQUIRES((std::is_move_constructible_v<T> &&
84 std::is_move_assignable_v<T>))
85class Lazy
86{
87public:
91 Lazy();
92
93
99 Lazy(const Lazy &other);
100
101
108 Lazy(Lazy &&other) noexcept;
109
110
118 Lazy &
119 operator=(const Lazy &other);
120
121
130 Lazy &
131 operator=(Lazy &&other) noexcept;
132
133
137 void
138 reset() noexcept;
139
140
159 template <typename Callable>
160 void
161 ensure_initialized(const Callable &creator) const
162 DEAL_II_CXX20_REQUIRES((std::is_invocable_r_v<T, Callable>));
163
164
169 bool
170 has_value() const;
171
172
179 const T &
180 value() const;
181
182
189 T &
190 value();
191
192
210 template <typename Callable>
211 const T &
212 value_or_initialize(const Callable &creator) const
213 DEAL_II_CXX20_REQUIRES((std::is_invocable_r_v<T, Callable>));
214
215
221 template <typename Callable>
222 DEAL_II_ALWAYS_INLINE inline T &
223 value_or_initialize(const Callable &creator)
224 DEAL_II_CXX20_REQUIRES((std::is_invocable_r_v<T, Callable>));
225
226
230 std::size_t
231 memory_consumption() const;
232
233private:
237 mutable std::optional<T> object;
238
239
244 mutable std::atomic<bool> object_is_initialized;
245
246
250 mutable Threads::Mutex initialization_mutex;
251};
252
258// ------------------------------- inline functions --------------------------
259
260
261template <typename T>
262DEAL_II_CXX20_REQUIRES((std::is_move_constructible_v<T> &&
263 std::is_move_assignable_v<T>))
264inline Lazy<T>::Lazy()
265 : object_is_initialized(false)
266{}
267
268
269template <typename T>
270DEAL_II_CXX20_REQUIRES((std::is_move_constructible_v<T> &&
271 std::is_move_assignable_v<T>))
272inline Lazy<T>::Lazy(const Lazy &other)
273 : object(other.object)
274{
275 object_is_initialized.store(other.object_is_initialized.load());
276}
277
278
279template <typename T>
280DEAL_II_CXX20_REQUIRES((std::is_move_constructible_v<T> &&
281 std::is_move_assignable_v<T>))
282inline Lazy<T>::Lazy(Lazy &&other) noexcept
283 : object(std::move(other.object))
284{
285 object_is_initialized.store(other.object_is_initialized.load());
286
287 // Mark the other object as uninitialized. This is marginally non-trivial
288 // because moving from std::optional<T> does *not* result in an empty
289 // std::optional<T> but instead one that does contain a T, but one that
290 // has been moved from -- typically something akin to a default-initialized
291 // T. That seems undesirable, so reset everything to an empty state.
292 other.object_is_initialized.store(false);
293 other.object.reset();
294}
295
296
297template <typename T>
298DEAL_II_CXX20_REQUIRES((std::is_move_constructible_v<T> &&
299 std::is_move_assignable_v<T>))
300inline Lazy<T> &Lazy<T>::operator=(const Lazy &other)
301{
302 object = other.object;
303 object_is_initialized.store(other.object_is_initialized.load());
304 return *this;
305}
306
307
308template <typename T>
309DEAL_II_CXX20_REQUIRES((std::is_move_constructible_v<T> &&
310 std::is_move_assignable_v<T>))
311inline Lazy<T> &Lazy<T>::operator=(Lazy &&other) noexcept
312{
313 object = std::move(other.object);
314 object_is_initialized.store(other.object_is_initialized.load());
315
316 // Mark the other object as uninitialized. This is marginally non-trivial
317 // because moving from std::optional<T> does *not* result in an empty
318 // std::optional<T> but instead one that does contain a T, but one that
319 // has been moved from -- typically something akin to a default-initialized
320 // T. That seems undesirable, so reset everything to an empty state.
321 other.object_is_initialized.store(false);
322 other.object.reset();
323
324 return *this;
325}
326
327
328template <typename T>
329DEAL_II_CXX20_REQUIRES((std::is_move_constructible_v<T> &&
330 std::is_move_assignable_v<T>))
331inline void Lazy<T>::reset() noexcept
332{
333 object_is_initialized.store(false);
334 object.reset();
335}
336
337
338template <typename T>
339DEAL_II_CXX20_REQUIRES((std::is_move_constructible_v<T> &&
340 std::is_move_assignable_v<T>))
341template <typename Callable>
343 void Lazy<T>::ensure_initialized(const Callable &creator) const
344 DEAL_II_CXX20_REQUIRES((std::is_invocable_r_v<T, Callable>))
345{
346 //
347 // Use Schmidt's double checking [1] for checking and initializing the
348 // object.
349 //
350 // [1] https://en.wikipedia.org/wiki/Double-checked_locking
351 //
352
353 //
354 // Check the object_is_initialized atomic with "acquire" semantics [1].
355 //
356 // This ensures that (a) all subsequent reads (of the object) are
357 // ordered after this check, and that (b) all writes to the object
358 // before the atomic bool was set to true with "release" semantics are
359 // visible on this thread.
360 //
361 // [1]
362 // https://en.cppreference.com/w/cpp/atomic/memory_order#Release-Acquire_ordering
363 //
364 if (!object_is_initialized.load(std::memory_order_acquire))
365#ifdef DEAL_II_HAVE_CXX20
366 [[unlikely]]
367#endif
368 {
369 std::lock_guard<std::mutex> lock(initialization_mutex);
370
371 //
372 // Check again. If this thread won the race to the lock then we
373 // initialize the object. Otherwise another thread has already
374 // initialized the object and flipped the object_is_initialized
375 // bit. (Here, the initialization_mutex ensures consistent ordering
376 // with a memory fence, so we will observe the updated bool without
377 // acquire semantics.)
378 //
379 if (!object_is_initialized.load(std::memory_order_relaxed))
380 {
381 Assert(object.has_value() == false, ExcInternalError());
382 object.emplace(std::move(creator()));
383
384 //
385 // Flip the object_is_initialized boolean with "release"
386 // semantics [1].
387 //
388 // This ensures that the above move is visible on all threads
389 // before checking the atomic bool with acquire semantics.
390 //
391 object_is_initialized.store(true, std::memory_order_release);
392 }
393 }
394
395 Assert(
396 object.has_value(),
398 "The internal std::optional<T> object does not contain a valid object "
399 "even though we have just initialized it."));
400}
401
402
403template <typename T>
404DEAL_II_CXX20_REQUIRES((std::is_move_constructible_v<T> &&
405 std::is_move_assignable_v<T>))
406inline DEAL_II_ALWAYS_INLINE bool Lazy<T>::has_value() const
407{
408 //
409 // In principle it would be sufficient to solely check the atomic<bool>
410 // object_is_initialized because the load() is performed with "acquire"
411 // semantics. But just in case let's check the object.has_value() boolean
412 // as well:
413 //
414 return (object_is_initialized && object.has_value());
415}
416
417
418template <typename T>
419DEAL_II_CXX20_REQUIRES((std::is_move_constructible_v<T> &&
420 std::is_move_assignable_v<T>))
421inline DEAL_II_ALWAYS_INLINE const T &Lazy<T>::value() const
422{
423 Assert(
424 object_is_initialized && object.has_value(),
426 "value() has been called but the contained object has not been "
427 "initialized. Did you forget to call 'ensure_initialized()' first?"));
428
429 return object.value();
430}
431
432
433template <typename T>
434DEAL_II_CXX20_REQUIRES((std::is_move_constructible_v<T> &&
435 std::is_move_assignable_v<T>))
436inline DEAL_II_ALWAYS_INLINE T &Lazy<T>::value()
437{
438 Assert(
439 object_is_initialized && object.has_value(),
441 "value() has been called but the contained object has not been "
442 "initialized. Did you forget to call 'ensure_initialized()' first?"));
443
444 return object.value();
445}
446
447
448template <typename T>
449DEAL_II_CXX20_REQUIRES((std::is_move_constructible_v<T> &&
450 std::is_move_assignable_v<T>))
451template <typename Callable>
452inline DEAL_II_ALWAYS_INLINE const T &Lazy<T>::value_or_initialize(
453 const Callable &creator) const
454 DEAL_II_CXX20_REQUIRES((std::is_invocable_r_v<T, Callable>))
455{
456 ensure_initialized(creator);
457 return object.value();
458}
459
460
461template <typename T>
462DEAL_II_CXX20_REQUIRES((std::is_move_constructible_v<T> &&
463 std::is_move_assignable_v<T>))
464template <typename Callable>
465inline DEAL_II_ALWAYS_INLINE T &Lazy<T>::value_or_initialize(
466 const Callable &creator)
467 DEAL_II_CXX20_REQUIRES((std::is_invocable_r_v<T, Callable>))
468{
469 ensure_initialized(creator);
470 return object.value();
471}
472
473
474template <typename T>
475DEAL_II_CXX20_REQUIRES((std::is_move_constructible_v<T> &&
476 std::is_move_assignable_v<T>))
477std::size_t Lazy<T>::memory_consumption() const
478{
479 return MemoryConsumption::memory_consumption(object) + //
480 sizeof(*this) - sizeof(object);
481}
482
483
485#endif
#define DEAL_II_ALWAYS_INLINE
Definition config.h:109
#define DEAL_II_NAMESPACE_OPEN
Definition config.h:502
#define DEAL_II_CXX20_REQUIRES(condition)
Definition config.h:177
#define DEAL_II_NAMESPACE_CLOSE
Definition config.h:503
#define Assert(cond, exc)
static ::ExceptionBase & ExcInternalError()
static ::ExceptionBase & ExcMessage(std::string arg1)
std::enable_if_t< std::is_fundamental_v< T >, std::size_t > memory_consumption(const T &t)