Skip to content

Commit f0b28ca

Browse files
committed
use atomics for explicit bucket histogram internals
1 parent a74262e commit f0b28ca

File tree

4 files changed

+283
-125
lines changed

4 files changed

+283
-125
lines changed

CHANGELOG.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,7 @@ This project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.htm
4545
- The default `TranslationStrategy` in `go.opentelemetry.io/exporters/prometheus` is changed from `otlptranslator.NoUTF8EscapingWithSuffixes` to `otlptranslator.UnderscoreEscapingWithSuffixes`. (#7421)
4646
- The `ErrorType` function in `go.opentelemetry.io/otel/semconv/v1.37.0` now handles custom error types.
4747
If an error implements an `ErrorType() string` method, the return value of that method will be used as the error type. (#7442)
48-
- Improve performance of concurrent measurements in `go.opentelemetry.io/otel/sdk/metric`. (#7427)
48+
- Improve performance of concurrent measurements in `go.opentelemetry.io/otel/sdk/metric`. (#7427, #7474)
4949

5050
<!-- Released section -->
5151
<!-- Don't change this section unless doing release -->

sdk/metric/internal/aggregate/atomic.go

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,69 @@ func (n *atomicCounter[N]) add(value N) {
5151
}
5252
}
5353

54+
// reset resets the internal state, and is not safe to call concurrently.
55+
func (n *atomicCounter[N]) reset() {
56+
n.nFloatBits.Store(0)
57+
n.nInt.Store(0)
58+
}
59+
60+
// atomicIntOrFloat is an atomic type that can be an int64 or float64.
61+
type atomicIntOrFloat[N int64 | float64] struct {
62+
// nFloatBits contains the float bits if N is float64.
63+
nFloatBits atomic.Uint64
64+
// nInt contains the int64 if N is int64
65+
nInt atomic.Int64
66+
}
67+
68+
func (n *atomicIntOrFloat[N]) load() (value N) {
69+
switch any(value).(type) {
70+
case int64:
71+
value = N(n.nInt.Load())
72+
case float64:
73+
value = N(math.Float64frombits(n.nFloatBits.Load()))
74+
}
75+
return value
76+
}
77+
78+
func (n *atomicIntOrFloat[N]) compareAndSwap(oldVal, newVal N) bool {
79+
switch any(oldVal).(type) {
80+
case float64:
81+
return n.nFloatBits.CompareAndSwap(math.Float64bits(float64(oldVal)), math.Float64bits(float64(newVal)))
82+
default:
83+
return n.nInt.CompareAndSwap(int64(oldVal), int64(newVal))
84+
}
85+
}
86+
87+
type atomicMinMax[N int64 | float64] struct {
88+
minimum atomicIntOrFloat[N]
89+
maximum atomicIntOrFloat[N]
90+
isSet atomic.Bool
91+
}
92+
93+
func (n *atomicMinMax[N]) observe(value N) {
94+
isSet := n.isSet.Load()
95+
for {
96+
minLoaded := n.minimum.load()
97+
if ((!isSet && minLoaded == 0) || value < minLoaded) && !n.minimum.compareAndSwap(minLoaded, value) {
98+
// We got a new min value, but lost the race. Try again.
99+
continue
100+
}
101+
maxLoaded := n.maximum.load()
102+
if ((!isSet && minLoaded == 0) || value > maxLoaded) && !n.maximum.compareAndSwap(maxLoaded, value) {
103+
// We got a new max value, but lost the race. Try again.
104+
continue
105+
}
106+
break
107+
}
108+
if !isSet {
109+
n.isSet.Store(true)
110+
}
111+
}
112+
113+
func (n *atomicMinMax[N]) load() (minimum, maximum N, ok bool) {
114+
return n.minimum.load(), n.maximum.load(), n.isSet.Load()
115+
}
116+
54117
// hotColdWaitGroup is a synchronization primitive which enables lockless
55118
// writes for concurrent writers and enables a reader to acquire exclusive
56119
// access to a snapshot of state including only completed operations.

0 commit comments

Comments
 (0)