Skip to content

Commit 26e46ec

Browse files
authored
Hashing improvements (#532)
* perf: optimise CounterVec hashing, enable other hashers Signed-off-by: Liam Gray <[email protected]> * Remove nohash-hasher dependency Signed-off-by: Liam Gray <[email protected]> * Restrict nohash visibility explicitly Signed-off-by: Liam Gray <[email protected]> --------- Signed-off-by: Liam Gray <[email protected]>
1 parent e17c5ce commit 26e46ec

File tree

4 files changed

+66
-13
lines changed

4 files changed

+66
-13
lines changed

benches/counter.rs

+27-3
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,8 @@
1111
// See the License for the specific language governing permissions and
1212
// limitations under the License.
1313

14-
use criterion::{criterion_group, criterion_main, Criterion};
14+
use criterion::{black_box, criterion_group, criterion_main, Criterion};
15+
use fnv::FnvBuildHasher;
1516
use prometheus::{Counter, CounterVec, IntCounter, Opts};
1617
use std::collections::HashMap;
1718
use std::sync::{atomic, Arc};
@@ -24,7 +25,11 @@ fn bench_counter_with_label_values(c: &mut Criterion) {
2425
)
2526
.unwrap();
2627
c.bench_function("counter_with_label_values", |b| {
27-
b.iter(|| counter.with_label_values(&["eins", "zwei", "drei"]).inc())
28+
b.iter(|| {
29+
counter
30+
.with_label_values(&black_box(["eins", "zwei", "drei"]))
31+
.inc()
32+
})
2833
});
2934
}
3035

@@ -41,7 +46,25 @@ fn bench_counter_with_mapped_labels(c: &mut Criterion) {
4146
labels.insert("two", "zwei");
4247
labels.insert("one", "eins");
4348
labels.insert("three", "drei");
44-
counter.with(&labels).inc();
49+
counter.with(&black_box(labels)).inc();
50+
})
51+
});
52+
}
53+
54+
fn bench_counter_with_mapped_labels_fnv(c: &mut Criterion) {
55+
let counter = CounterVec::new(
56+
Opts::new("benchmark_counter", "A counter to benchmark it."),
57+
&["one", "two", "three"],
58+
)
59+
.unwrap();
60+
61+
c.bench_function("counter_with_mapped_labels_fnv", |b| {
62+
b.iter(|| {
63+
let mut labels = HashMap::with_capacity_and_hasher(3, FnvBuildHasher::default());
64+
labels.insert("two", "zwei");
65+
labels.insert("one", "eins");
66+
labels.insert("three", "drei");
67+
counter.with(&black_box(labels)).inc();
4568
})
4669
});
4770
}
@@ -192,6 +215,7 @@ criterion_group!(
192215
bench_counter_with_label_values,
193216
bench_counter_with_label_values_concurrent_write,
194217
bench_counter_with_mapped_labels,
218+
bench_counter_with_mapped_labels_fnv,
195219
bench_counter_with_prepared_mapped_labels,
196220
bench_int_counter_no_labels,
197221
bench_int_counter_no_labels_concurrent_write,

src/lib.rs

+1
Original file line numberDiff line numberDiff line change
@@ -147,6 +147,7 @@ mod errors;
147147
mod gauge;
148148
mod histogram;
149149
mod metrics;
150+
mod nohash;
150151
mod pulling_gauge;
151152
#[cfg(feature = "push")]
152153
mod push;

src/nohash.rs

+23
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
use std::hash::{BuildHasherDefault, Hasher};
2+
3+
/// Inspired by nohash-hasher, but we avoid the crate dependency because it's in public archive.
4+
#[derive(Copy, Clone, Debug, Default)]
5+
pub(crate) struct NoHashHasher(u64);
6+
7+
pub(crate) type BuildNoHashHasher = BuildHasherDefault<NoHashHasher>;
8+
9+
impl Hasher for NoHashHasher {
10+
#[inline]
11+
fn finish(&self) -> u64 {
12+
self.0
13+
}
14+
15+
fn write(&mut self, _bytes: &[u8]) {
16+
panic!("Invalid use of NoHashHasher");
17+
}
18+
19+
#[inline]
20+
fn write_u64(&mut self, i: u64) {
21+
self.0 = i;
22+
}
23+
}

src/vec.rs

+15-10
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
// Copyright 2019 TiKV Project Authors. Licensed under Apache-2.0.
33

44
use std::collections::HashMap;
5-
use std::hash::Hasher;
5+
use std::hash::{BuildHasher, Hasher};
66
use std::sync::Arc;
77

88
use fnv::FnvHasher;
@@ -11,6 +11,7 @@ use parking_lot::RwLock;
1111
use crate::desc::{Desc, Describer};
1212
use crate::errors::{Error, Result};
1313
use crate::metrics::{Collector, Metric};
14+
use crate::nohash::BuildNoHashHasher;
1415
use crate::proto::{MetricFamily, MetricType};
1516

1617
/// An interface for building a metric vector.
@@ -26,7 +27,8 @@ pub trait MetricVecBuilder: Send + Sync + Clone {
2627

2728
#[derive(Debug)]
2829
pub(crate) struct MetricVecCore<T: MetricVecBuilder> {
29-
pub children: RwLock<HashMap<u64, T::M>>,
30+
// the key is pre-hashed, and so we use a no-hash hasher to avoid hashing again.
31+
pub children: RwLock<HashMap<u64, T::M, BuildNoHashHasher>>,
3032
pub desc: Desc,
3133
pub metric_type: MetricType,
3234
pub new_metric: T,
@@ -62,7 +64,7 @@ impl<T: MetricVecBuilder> MetricVecCore<T> {
6264
self.get_or_create_metric(h, vals)
6365
}
6466

65-
pub fn get_metric_with<V>(&self, labels: &HashMap<&str, V>) -> Result<T::M>
67+
pub fn get_metric_with<V, S: BuildHasher>(&self, labels: &HashMap<&str, V, S>) -> Result<T::M>
6668
where
6769
V: AsRef<str> + std::fmt::Debug,
6870
{
@@ -90,7 +92,7 @@ impl<T: MetricVecBuilder> MetricVecCore<T> {
9092
Ok(())
9193
}
9294

93-
pub fn delete<V>(&self, labels: &HashMap<&str, V>) -> Result<()>
95+
pub fn delete<V, S: BuildHasher>(&self, labels: &HashMap<&str, V, S>) -> Result<()>
9496
where
9597
V: AsRef<str> + std::fmt::Debug,
9698
{
@@ -128,7 +130,7 @@ impl<T: MetricVecBuilder> MetricVecCore<T> {
128130
Ok(h.finish())
129131
}
130132

131-
fn hash_labels<V>(&self, labels: &HashMap<&str, V>) -> Result<u64>
133+
fn hash_labels<V, S: BuildHasher>(&self, labels: &HashMap<&str, V, S>) -> Result<u64>
132134
where
133135
V: AsRef<str> + std::fmt::Debug,
134136
{
@@ -155,7 +157,10 @@ impl<T: MetricVecBuilder> MetricVecCore<T> {
155157
Ok(h.finish())
156158
}
157159

158-
fn get_label_values<'a, V>(&'a self, labels: &'a HashMap<&str, V>) -> Result<Vec<&'a str>>
160+
fn get_label_values<'a, V, S: BuildHasher>(
161+
&'a self,
162+
labels: &'a HashMap<&str, V, S>,
163+
) -> Result<Vec<&'a str>>
159164
where
160165
V: AsRef<str> + std::fmt::Debug,
161166
{
@@ -212,7 +217,7 @@ impl<T: MetricVecBuilder> MetricVec<T> {
212217
pub fn create(metric_type: MetricType, new_metric: T, opts: T::P) -> Result<MetricVec<T>> {
213218
let desc = opts.describe()?;
214219
let v = MetricVecCore {
215-
children: RwLock::new(HashMap::new()),
220+
children: RwLock::new(HashMap::default()),
216221
desc,
217222
metric_type,
218223
new_metric,
@@ -264,7 +269,7 @@ impl<T: MetricVecBuilder> MetricVec<T> {
264269
/// This method is used for the same purpose as
265270
/// `get_metric_with_label_values`. See there for pros and cons of the two
266271
/// methods.
267-
pub fn get_metric_with<V>(&self, labels: &HashMap<&str, V>) -> Result<T::M>
272+
pub fn get_metric_with<V, S: BuildHasher>(&self, labels: &HashMap<&str, V, S>) -> Result<T::M>
268273
where
269274
V: AsRef<str> + std::fmt::Debug,
270275
{
@@ -294,7 +299,7 @@ impl<T: MetricVecBuilder> MetricVec<T> {
294299
/// `with` works as `get_metric_with`, but panics if an error occurs. The method allows
295300
/// neat syntax like:
296301
/// httpReqs.with(Labels{"status":"404", "method":"POST"}).inc()
297-
pub fn with<V>(&self, labels: &HashMap<&str, V>) -> T::M
302+
pub fn with<V, S: BuildHasher>(&self, labels: &HashMap<&str, V, S>) -> T::M
298303
where
299304
V: AsRef<str> + std::fmt::Debug,
300305
{
@@ -328,7 +333,7 @@ impl<T: MetricVecBuilder> MetricVec<T> {
328333
///
329334
/// This method is used for the same purpose as `delete_label_values`. See
330335
/// there for pros and cons of the two methods.
331-
pub fn remove<V>(&self, labels: &HashMap<&str, V>) -> Result<()>
336+
pub fn remove<V, S: BuildHasher>(&self, labels: &HashMap<&str, V, S>) -> Result<()>
332337
where
333338
V: AsRef<str> + std::fmt::Debug,
334339
{

0 commit comments

Comments
 (0)