Skip to content

Commit 6bcd096

Browse files
perf: avoid allocating a mapping lambda on every labelValues() call
labelValues() went straight to data.computeIfAbsent(key, l -> ...). The mapping function captures 'this', so a new lambda instance was allocated on every call - including the common case where the data point already exists, since the lambda argument is constructed before computeIfAbsent runs. Add a data.get(key) fast path that returns the existing data point without constructing the lambda. In a JMH benchmark of a histogram-heavy workload this cut record-path allocation by ~18% (exactly the 16-byte captured lambda per observation). On the miss path, validate the label values on the raw array before computeIfAbsent, so the null check runs outside the ConcurrentHashMap bin lock and without List indirection. Creation stays inside computeIfAbsent: newDataPoint() has side effects (a native histogram may schedule a reset task), so at-most-once creation must be preserved. Behavior is unchanged (verified by the core tests, including StatefulMetricTest). Signed-off-by: David Mollitor <david.mollitor@databricks.com>
1 parent f71915f commit 6bcd096

1 file changed

Lines changed: 14 additions & 14 deletions

File tree

‎prometheus-metrics-core/src/main/java/io/prometheus/metrics/core/metrics/StatefulMetric.java‎

Lines changed: 14 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -116,20 +116,20 @@ public D labelValues(String... labelValues) {
116116
"Expected " + labelNames.length + " label values, but got " + labelValues.length + ".");
117117
}
118118
}
119-
return data.computeIfAbsent(
120-
Arrays.asList(labelValues),
121-
l -> {
122-
for (int i = 0; i < l.size(); i++) {
123-
if (l.get(i) == null) {
124-
throw new IllegalArgumentException(
125-
"null label value for metric "
126-
+ metadata.getName()
127-
+ " and label "
128-
+ labelNames[i]);
129-
}
130-
}
131-
return newDataPoint();
132-
});
119+
List<String> key = Arrays.asList(labelValues);
120+
// Fast path: the data point for these label values almost always already exists, since the same
121+
// label combinations are reused across updates.
122+
T dataPoint = data.get(key);
123+
if (dataPoint != null) {
124+
return dataPoint;
125+
}
126+
for (int i = 0; i < labelValues.length; i++) {
127+
if (labelValues[i] == null) {
128+
throw new IllegalArgumentException(
129+
"null label value for metric " + metadata.getName() + " and label " + labelNames[i]);
130+
}
131+
}
132+
return data.computeIfAbsent(key, l -> newDataPoint());
133133
}
134134

135135
/**

0 commit comments

Comments
 (0)