perfc 0.11.0
Loading...
Searching...
No Matches
counter.hpp
Go to the documentation of this file.
1/**
2 * @file
3 * @ingroup perfc_counter
4 * @copyright (c) Copyright ESO 2024
5 * All Rights Reserved
6 * ESO (eso.org) is an Intergovernmental Organisation, and therefore special legal conditions apply.
7 *
8 * @brief Performance counter implementation.
9 *
10 *
11 * @defgroup perfc_counter Counters
12 * @ingroup perfc
13 * @brief Atomic counters types.
14 *
15 * For an introduction with examples see @ref counters.
16 *
17 * Three template specializations are provided. All satisfy the basic concept
18 * @ref concept_atomic_counter :
19 *
20 * <table>
21 * <tr>
22 * <th>`T` constraints</th>
23 * <th>`Clock` constraints</th>
24 * <th>`Counter::ValueType`</th>
25 * <th>Notes</th>
26 * <th>Link</th>
27 * </tr>
28 * <tr>
29 * <td>Integral</td>
30 * <td>-</td>
31 * <td>`T`</td>
32 * <td>
33 * - Specialization is lock-free if `std::atomic<T>` is lock-free.
34 * - Specialization can be fully lock-free on most architectures.
35 * - Provides additional member functions like pre/post increment, `FetchAdd`/`FetchSub`.
36 * - No internal timestamp.
37 * </td>
38 * <td>@ref counter_integral "Counter<T>" </td>
39 * </tr>
40 * <tr>
41 * <td>Non-Integral</td>
42 * <td>-</td>
43 * <td>`T`</td>
44 * <td>
45 * - Specialization is lock-free if `std::atomic<T>` is lock-free.
46 * - No internal timestamp.
47 * </td>
48 * <td>@ref counter_nonintegral "Counter<T>" </td>
49 * </tr>
50 * <tr>
51 * <td>None</td>
52 * <td>`Clock` satisfy *TrivialClock*</td>
53 * <td>@ref perfc::Timestamped "Timestamped<T, TimePoint>"</td>
54 * <td>
55 * - Default `Clock` is `std::chrono::steady_clock`.
56 * - Keeps internal timestamp.
57 * - Automatically updates timestamp on writes, if time is not provided by caller.
58 * </td>
59 * <td>@ref counter_clock "Counter<T, Clock>" </td>
60 * </tr>
61 * </table>
62 *
63 * Commonly used types have pre-defined aliases for double, signed and unsigned 64bit integer, with
64 * and without timestamp, see list below.
65 *
66 * @note As counters are potential synchronization objects they provide no copy or move constructor.
67 */
68#ifndef PERFC_COUNTER_HPP_
69#define PERFC_COUNTER_HPP_
70#include <atomic>
71#include <chrono>
72#include <type_traits>
73
74namespace perfc {
75
76/**
77 * Memory ordering constraints
78 * @relatesalso Counter
79 * @ingroup perfc_counter
80 */
81enum class MemoryOrder : std::underlying_type_t<std::memory_order> {
82 /**
83 * Store operations synchronizes with corresponding Acquire.
84 */
85 Release = static_cast<std::underlying_type_t<std::memory_order>>(std::memory_order_release),
86
87 /**
88 * Load operations synchronizes with corresponding Release.
89 */
90 Acquire = static_cast<std::underlying_type_t<std::memory_order>>(std::memory_order_acquire),
91
92 /**
93 * No memory ordering constraints (no synchronization).
94 *
95 * Use @ref CounterRelease() and @ref CounterAcquire() for synchronization.
96 */
97 Relaxed = static_cast<std::underlying_type_t<std::memory_order>>(std::memory_order_relaxed),
98};
99
100/**
101 * Trivial type describing a counter value and associated timestamp.
102 *
103 * This type is used as the counter value for counters with internal timestamp.
104 *
105 * @tparam T Counter value type.
106 * @tparam TimePoint Timestamp type.
107 * @ingroup perfc_counter
108 * @headerfile <> <perfc/counter.hpp>
109 * @relatesalso Counter
110 */
111template <class T, class TimePoint>
114 TimePoint timestamp;
115};
116
117template <class T, class Clock = std::chrono::steady_clock, class = void>
119
120/**
121 * @brief **Integrals w/o timestamp** Partial specialization for integral types T and without
122 * clock.
123 * @anchor counter_integral
124 * @headerfile <> <perfc/counter.hpp>
125 *
126 * Represents a thread safe performance counter primitive of type T.
127 *
128 * @note This is the only Counter specialization that is completely lock-free on most architectures.
129 *
130 * Satisfies @ref concept_atomic_counter requirements.
131 *
132 * @thread_safe{All operations are thread safe but not necessarily synchronized}
133 *
134 * @tparam T counter value type.
135 * Type requirements:
136 * - `std::is_integral<T>::value == true`
137 * @ingroup perfc_counter
138 */
139template <class T>
140class Counter<T, void, typename std::enable_if<std::is_integral<T>::value>::type> {
141public:
142 using ClockType = void;
143 using TimePointType = void;
144 using ValueType = T;
145 using CounterType = std::atomic<T>;
146
147 /**
148 * Is true if counter is always lock free.
149 */
150 static constexpr bool IS_ALWAYS_LOCK_FREE = CounterType::is_always_lock_free;
151
152 /**
153 * Construct with value-initialized T (`T()`)
154 */
155 explicit Counter() noexcept : Counter(T()) {
156 }
157
158 /**
159 * Construct with initial value @a value.
160 *
161 * @param value Initial value.
162 */
163 explicit Counter(T value) noexcept : m_value(value) {
164 }
165 Counter(Counter const&) = delete;
166
167 /**
168 * Query if operations are lock free.
169 * @returns true if operations are lock free.
170 * @return false otherwise.
171 */
172 bool IsLockFree() const noexcept {
173 return m_value.is_lock_free();
174 }
175
176 /**
177 * Stores provided @a value in counter.
178 *
179 * @param value Counter value to store.
180 * @param order Memory order constraints.
181 */
182 void Store(T value, MemoryOrder order = MemoryOrder::Release) noexcept {
183 return m_value.store(value, static_cast<std::memory_order>(order));
184 }
185
186 /**
187 * Load value from counter.
188 *
189 * @param order Memory order constraints.
190 * @return Counter value.
191 */
192 [[nodiscard]] T Load(MemoryOrder order = MemoryOrder::Acquire) const noexcept {
193 return m_value.load(static_cast<std::memory_order>(order));
194 }
195
196 /**
197 * Perform post-increment with current value and @a value.
198 *
199 * @param value value to add.
200 * @param order Memory order constraints.
201 * @return Counter value immediately preceding the addition of @a value.
202 */
203 T FetchAdd(T value, MemoryOrder order = MemoryOrder::Release) noexcept {
204 return m_value.fetch_add(value, static_cast<std::memory_order>(order));
205 }
206
207 /**
208 * Load value from counter.
209 *
210 * @param value value to subtract.
211 * @param order Memory order constraints.
212 * @return Counter value immediately preceding the addition of @a value.
213 * @return Counter value.
214 */
215 T FetchSub(T value, MemoryOrder order = MemoryOrder::Release) noexcept {
216 return m_value.fetch_sub(value, static_cast<std::memory_order>(order));
217 }
218
219 /**
220 * Pre-increment operator using default memory order.
221 */
222 T operator++() noexcept {
223 return FetchAdd(1) + 1;
224 }
225
226 /**
227 * Post-increment operator using default memory order.
228 *
229 * @returns Counter value before incrementation.
230 */
231 T operator++(int) noexcept {
232 return FetchAdd(1);
233 }
234
235 /**
236 * Pre-decrement operator using default memory order.
237 */
238 T operator--() noexcept {
239 return FetchSub(1) - 1;
240 }
241
242 /**
243 * Post-decrement operator using default memory order.
244 *
245 * @returns Counter value before decrementation.
246 */
247 T operator--(int) noexcept {
248 return FetchSub(1);
249 }
250
251 /**
252 * Performs addition (equivalent to `FetchAdd(value) + value`
253 *
254 * @param value value to add.
255 * @returns Result of arithmetic operation.
256 */
257 T operator+=(T value) noexcept {
258 return FetchAdd(value) + value;
259 }
260
261 /**
262 * Performs subtraction (equivalent to `FetchSub(value) - value`
263 *
264 * @param value value to subtract.
265 * @returns Result of arithmetic operation.
266 */
267 T operator-=(T value) noexcept {
268 return FetchSub(value) - value;
269 }
270
271private:
272 CounterType m_value;
273};
274
275/**
276 * @brief **Non-integrals w/o timestamp** Partial specialization matching void clocks and
277 * non-integral T's.
278 * @anchor counter_nonintegral
279 * @headerfile <> <perfc/counter.hpp>
280 *
281 * Satisfies @ref concept_atomic_counter requirements.
282 *
283 * @ingroup perfc_counter
284 */
285template <class T>
286class Counter<T, void, typename std::enable_if<!std::is_integral<T>::value>::type> {
287public:
288 using ClockType = void;
289 using TimePointType = void;
290 using ValueType = T;
291 using CounterType = std::atomic<ValueType>;
292 /**
293 * Is true if counter is always lock free.
294 */
295 static constexpr bool IS_ALWAYS_LOCK_FREE = CounterType::is_always_lock_free;
296
297 /**
298 * Construct with value-initialized T (`T()`)
299 */
300 explicit Counter() noexcept : Counter(T()) {
301 }
302
303 /**
304 * Initialze with provided value.
305 *
306 * @param value Initial value.
307 */
308 explicit Counter(T value) noexcept : m_value(value) {
309 }
310
311 Counter(Counter const&) = delete;
312
313 /**
314 * Query if operations are lock free.
315 * @returns true if operations are lock free.
316 * @return false otherwise.
317 */
318 bool IsLockFree() const noexcept {
319 return m_value.is_lock_free();
320 }
321
322 /**
323 * Store value in counter.
324 *
325 * @param value New counter value to store.
326 * @param order Memory order constraints.
327 */
328 void Store(T value, MemoryOrder order = MemoryOrder::Release) noexcept {
329 return m_value.store(value, static_cast<std::memory_order>(order));
330 }
331
332 /**
333 * Load value from counter.
334 *
335 * @param order Memory order constraints.
336 * @returns Counter value.
337 */
338 [[nodiscard]] ValueType Load(MemoryOrder order = MemoryOrder::Acquire) const noexcept {
339 return m_value.load(static_cast<std::memory_order>(order));
340 }
341
342private:
343 CounterType m_value;
344};
345
346/**
347 * @brief **With timestamp** Partial specialization for non-void Clock type.
348 * @anchor counter_clock
349 * @headerfile <> <perfc/counter.hpp>
350 *
351 * @thread_safe{All operations are thread safe but not necessarily synchronized}
352 *
353 * Satisfies @ref concept_atomic_counter requirements.
354 *
355 * @tparam T counter value type.
356 * Type requirements:
357 * - *TriviallyCopyable*
358 * - *CopyConstructible*
359 * - *CopyAssignable*
360 * @tparam Clock a clock satisfying @a TrivialClock requirements.
361 * @ingroup perfc_counter
362 */
363template <class T, class Clock>
364class Counter<T, Clock, typename std::enable_if<!std::is_void<Clock>::value>::type> {
365public:
366 using ClockType = Clock;
367 using TimePointType = typename ClockType::time_point;
369 using CounterType = std::atomic<ValueType>;
370
371 /**
372 * Is true if counter is always lock free.
373 */
374 static constexpr bool IS_ALWAYS_LOCK_FREE = CounterType::is_always_lock_free;
375
376 /**
377 * Construct with value-initialized T (`T()`)
378 */
379 explicit Counter() noexcept : Counter(T()) {
380 }
381
382 /**
383 * Initialze with a value using current time.
384 *
385 * @param value Initial value.
386 */
387 explicit Counter(T value) noexcept : m_value({value, ClockType::now()}) {
388 }
389
390 /**
391 * Initialze with value and time.
392 *
393 * @param value Initial value and timestamp.
394 */
395 explicit Counter(ValueType value) noexcept : m_value(value) {
396 }
397
398 Counter(Counter const&) = delete;
399
400 /**
401 * Query if operations are lock free.
402 * @returns true if operations are lock free.
403 * @return false otherwise.
404 */
405 bool IsLockFree() const noexcept {
406 return m_value.is_lock_free();
407 }
408
409 /**
410 * Stores provided @a value and with current time by querying clock.
411 *
412 * @param value Counter value to store.
413 * @param order Memory order constraints.
414 */
415 void Store(T value, MemoryOrder order = MemoryOrder::Release) noexcept {
416 return m_value.store({value, ClockType::now()}, static_cast<std::memory_order>(order));
417 }
418
419 /**
420 * Store value with provided timestamp in counter.
421 *
422 * @param value Counter value and timestamp to store.
423 * @param order Memory order constraints.
424 */
425 void Store(ValueType value, MemoryOrder order = MemoryOrder::Release) noexcept {
426 return m_value.store(value, static_cast<std::memory_order>(order));
427 }
428
429 /**
430 * Load value and timestamp from counter.
431 *
432 * @param order Memory order constraints.
433 * @return Counter value with timestamp.
434 */
435 [[nodiscard]] ValueType Load(MemoryOrder order = MemoryOrder::Acquire) const noexcept {
436 return m_value.load(static_cast<std::memory_order>(order));
437 }
438
439private:
440 CounterType m_value;
441};
442
443/**
444 * @name Synchronization functions
445 *
446 * Provides capability to synchronize relaxed atomic counter reads and writes.
447 */
448/// @{
449/**
450 * @a Synchronizes-with @ref CounterRelease() or Counter store operations using
451 * MemoryOrder::Release.
452 *
453 * CounterRelease() is only necessary ensure that preceding relaxed operations to
454 * Counter are synchronized with reads.
455 *
456 * @ingroup perfc_counter
457 */
458inline void CounterAcquire() noexcept {
459 std::atomic_thread_fence(std::memory_order_acquire);
460}
461
462/**
463 * @a Synchronizes-with @ref CounterAcquire() or counter load operations using
464 * MemoryOrder::Acquire.
465 *
466 * CounterRelease() is only necessary ensure that preceding relaxed operations to
467 * Counter are synchronized with reads.
468 *
469 * @ingroup perfc_counter
470 */
471inline void CounterRelease() noexcept {
472 std::atomic_thread_fence(std::memory_order_release);
473}
474
475/// @}
476
477/** @name Aliases of timestamped counters
478 *
479 * These aliases are using the clock @c std::steady_clock as a timestamp source.
480 * @ingroup perfc_counter
481 */
482/// @{
483/** @ingroup perfc_counter */
487/// @}
488
489/** @name Aliases of non-timestamped counters
490 *
491 * These counters aliases have no internal timestamp and are *very* light weight and on most
492 * 64bit architectures they are fully lock-free.
493 *
494 * @ingroup perfc_counter
495 */
496/// @{
497/** @ingroup perfc_counter */
501/// @}
502
503} // namespace perfc
504#endif // #ifndef PERFC_COUNTER_HPP_
void Store(ValueType value, MemoryOrder order=MemoryOrder::Release) noexcept
Store value with provided timestamp in counter.
Definition counter.hpp:425
ValueType Load(MemoryOrder order=MemoryOrder::Acquire) const noexcept
Load value and timestamp from counter.
Definition counter.hpp:435
void Store(T value, MemoryOrder order=MemoryOrder::Release) noexcept
Stores provided value and with current time by querying clock.
Definition counter.hpp:415
static constexpr bool IS_ALWAYS_LOCK_FREE
Is true if counter is always lock free.
Definition counter.hpp:374
Counter(T value) noexcept
Initialze with a value using current time.
Definition counter.hpp:387
T operator++(int) noexcept
Post-increment operator using default memory order.
Definition counter.hpp:231
T operator-=(T value) noexcept
Performs subtraction (equivalent to FetchSub(value) - value
Definition counter.hpp:267
T operator+=(T value) noexcept
Performs addition (equivalent to FetchAdd(value) + value
Definition counter.hpp:257
void Store(T value, MemoryOrder order=MemoryOrder::Release) noexcept
Stores provided value in counter.
Definition counter.hpp:182
T FetchAdd(T value, MemoryOrder order=MemoryOrder::Release) noexcept
Perform post-increment with current value and value.
Definition counter.hpp:203
T FetchSub(T value, MemoryOrder order=MemoryOrder::Release) noexcept
Load value from counter.
Definition counter.hpp:215
static constexpr bool IS_ALWAYS_LOCK_FREE
Is true if counter is always lock free.
Definition counter.hpp:150
bool IsLockFree() const noexcept
Query if operations are lock free.
Definition counter.hpp:172
T operator--() noexcept
Pre-decrement operator using default memory order.
Definition counter.hpp:238
T Load(MemoryOrder order=MemoryOrder::Acquire) const noexcept
Load value from counter.
Definition counter.hpp:192
T operator--(int) noexcept
Post-decrement operator using default memory order.
Definition counter.hpp:247
T operator++() noexcept
Pre-increment operator using default memory order.
Definition counter.hpp:222
void Store(T value, MemoryOrder order=MemoryOrder::Release) noexcept
Store value in counter.
Definition counter.hpp:328
bool IsLockFree() const noexcept
Query if operations are lock free.
Definition counter.hpp:318
ValueType Load(MemoryOrder order=MemoryOrder::Acquire) const noexcept
Load value from counter.
Definition counter.hpp:338
static constexpr bool IS_ALWAYS_LOCK_FREE
Is true if counter is always lock free.
Definition counter.hpp:295
void CounterRelease() noexcept
Synchronizes-with CounterAcquire() or counter load operations using MemoryOrder::Acquire.
Definition counter.hpp:471
MemoryOrder
Memory ordering constraints.
Definition counter.hpp:81
Counter< double, void > CounterDouble
Definition counter.hpp:498
Counter< std::uint64_t, std::chrono::steady_clock > CounterU64Ts
Definition counter.hpp:485
Counter< std::int64_t, void > CounterI64
Definition counter.hpp:500
Counter< std::uint64_t, void > CounterU64
Definition counter.hpp:499
Counter< double, std::chrono::steady_clock > CounterDoubleTs
Definition counter.hpp:484
void CounterAcquire() noexcept
Synchronizes-with CounterRelease() or Counter store operations using MemoryOrder::Release.
Definition counter.hpp:458
Counter< std::int64_t, std::chrono::steady_clock > CounterI64Ts
Definition counter.hpp:486
Trivial type describing a counter value and associated timestamp.
Definition counter.hpp:112