Use std::bit_cast, document that gauge mutex is an implementation detail

This commit is contained in:
2025-08-29 10:45:19 -04:00
parent de5adb54d2
commit 1133d1e365

View File

@@ -8,7 +8,7 @@
// //
// PRECISION STRATEGY: // PRECISION STRATEGY:
// - Use atomic<uint64_t> for lock-free storage // - Use atomic<uint64_t> for lock-free storage
// - Store doubles by reinterpreting bits as uint64_t (preserves full IEEE 754 // - Store doubles using std::bit_cast to uint64_t (preserves full IEEE 754
// precision) // precision)
// - Single writer assumption allows simple load/store without CAS loops // - Single writer assumption allows simple load/store without CAS loops
// //
@@ -19,9 +19,9 @@
#include <algorithm> #include <algorithm>
#include <atomic> #include <atomic>
#include <bit>
#include <cassert> #include <cassert>
#include <cstdint> #include <cstdint>
#include <cstring>
#include <limits> #include <limits>
#include <memory> #include <memory>
#include <mutex> #include <mutex>
@@ -74,15 +74,7 @@ using AtomicWord = std::atomic<uint64_t>;
// DESIGN: Store doubles in atomic<uint64_t> for lock-free operations // DESIGN: Store doubles in atomic<uint64_t> for lock-free operations
// - Preserves full IEEE 754 double precision (no truncation) // - Preserves full IEEE 754 double precision (no truncation)
// - Allows atomic load/store without locks // - Allows atomic load/store without locks
// - Safe bit-wise conversion between double and uint64_t // - Use std::bit_cast for safe conversion between double and uint64_t
template <class U, class V> U reinterpret(V v) {
static_assert(sizeof(U) == sizeof(V));
static_assert(std::is_arithmetic_v<U>);
static_assert(std::is_arithmetic_v<V>);
U u;
memcpy(&u, &v, sizeof(u));
return u;
}
// Family::State structures own the second-level maps (labels -> instances) // Family::State structures own the second-level maps (labels -> instances)
template <> struct Family<Counter>::State { template <> struct Family<Counter>::State {
@@ -235,11 +227,13 @@ void Counter::inc(double x) {
// DESIGN: Single writer per thread allows simple load-modify-store // DESIGN: Single writer per thread allows simple load-modify-store
// No CAS loop needed since only one thread writes to this counter // No CAS loop needed since only one thread writes to this counter
auto current_value = auto current_value =
reinterpret<double>(p->value.load(std::memory_order_relaxed)); std::bit_cast<double>(p->value.load(std::memory_order_relaxed));
p->value.store(reinterpret<uint64_t>(current_value + x), p->value.store(std::bit_cast<uint64_t>(current_value + x),
std::memory_order_relaxed); std::memory_order_relaxed);
} }
void Gauge::inc(double x) { void Gauge::inc(double x) {
// IMPLEMENTATION DETAIL: Gauges currently support multiple writers via mutex,
// though the public API documents single-writer semantics for consistency
std::unique_lock<std::mutex> _{p->mutex}; std::unique_lock<std::mutex> _{p->mutex};
p->value += x; p->value += x;
} }
@@ -263,8 +257,8 @@ void Histogram::observe(double x) {
// DESIGN: Single writer per thread allows simple load-modify-store for sum // DESIGN: Single writer per thread allows simple load-modify-store for sum
// No CAS loop needed since only one thread writes to this histogram // No CAS loop needed since only one thread writes to this histogram
auto current_sum = auto current_sum =
reinterpret<double>(p->sum.load(std::memory_order_relaxed)); std::bit_cast<double>(p->sum.load(std::memory_order_relaxed));
p->sum.store(reinterpret<uint64_t>(current_sum + x), p->sum.store(std::bit_cast<uint64_t>(current_sum + x),
std::memory_order_relaxed); std::memory_order_relaxed);
p->observations.fetch_add(1, std::memory_order_relaxed); p->observations.fetch_add(1, std::memory_order_relaxed);