Skip to content

Commit 947534e

Browse files
committed
perf: optimise CounterVec hashing, enable other hashers
Signed-off-by: Liam Gray <[email protected]>
1 parent f0c9bc2 commit 947534e

File tree

3 files changed

+36
-14
lines changed

3 files changed

+36
-14
lines changed

Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ push = ["reqwest", "libc", "protobuf"]
2424
[dependencies]
2525
cfg-if = "^1.0"
2626
fnv = "^1.0"
27+
nohash-hasher = "0.2.0"
2728
lazy_static = "^1.4"
2829
libc = { version = "^0.2", optional = true }
2930
parking_lot = "^0.12"

benches/counter.rs

Lines changed: 23 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -11,8 +11,9 @@
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};
1515
use prometheus::{Counter, CounterVec, IntCounter, Opts};
16+
use fnv::FnvBuildHasher;
1617
use std::collections::HashMap;
1718
use std::sync::{atomic, Arc};
1819
use std::thread;
@@ -24,7 +25,7 @@ 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(|| counter.with_label_values(&black_box(["eins", "zwei", "drei"])).inc())
2829
});
2930
}
3031

@@ -41,7 +42,25 @@ fn bench_counter_with_mapped_labels(c: &mut Criterion) {
4142
labels.insert("two", "zwei");
4243
labels.insert("one", "eins");
4344
labels.insert("three", "drei");
44-
counter.with(&labels).inc();
45+
counter.with(&black_box(labels)).inc();
46+
})
47+
});
48+
}
49+
50+
fn bench_counter_with_mapped_labels_fnv(c: &mut Criterion) {
51+
let counter = CounterVec::new(
52+
Opts::new("benchmark_counter", "A counter to benchmark it."),
53+
&["one", "two", "three"],
54+
)
55+
.unwrap();
56+
57+
c.bench_function("counter_with_mapped_labels_fnv", |b| {
58+
b.iter(|| {
59+
let mut labels = HashMap::with_capacity_and_hasher(3, FnvBuildHasher::default());
60+
labels.insert("two", "zwei");
61+
labels.insert("one", "eins");
62+
labels.insert("three", "drei");
63+
counter.with(&black_box(labels)).inc();
4564
})
4665
});
4766
}
@@ -192,6 +211,7 @@ criterion_group!(
192211
bench_counter_with_label_values,
193212
bench_counter_with_label_values_concurrent_write,
194213
bench_counter_with_mapped_labels,
214+
bench_counter_with_mapped_labels_fnv,
195215
bench_counter_with_prepared_mapped_labels,
196216
bench_int_counter_no_labels,
197217
bench_int_counter_no_labels_concurrent_write,

src/vec.rs

Lines changed: 12 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,10 @@
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;
7-
87
use fnv::FnvHasher;
8+
use nohash_hasher::BuildNoHashHasher;
99
use parking_lot::RwLock;
1010

1111
use crate::desc::{Desc, Describer};
@@ -26,7 +26,8 @@ pub trait MetricVecBuilder: Send + Sync + Clone {
2626

2727
#[derive(Debug)]
2828
pub(crate) struct MetricVecCore<T: MetricVecBuilder> {
29-
pub children: RwLock<HashMap<u64, T::M>>,
29+
// the key is pre-hashed, and so we use a no-hash hasher to avoid hashing again.
30+
pub children: RwLock<HashMap<u64, T::M, BuildNoHashHasher<u64>>>,
3031
pub desc: Desc,
3132
pub metric_type: MetricType,
3233
pub new_metric: T,
@@ -59,7 +60,7 @@ impl<T: MetricVecBuilder> MetricVecCore<T> {
5960
self.get_or_create_metric(h, vals)
6061
}
6162

62-
pub fn get_metric_with(&self, labels: &HashMap<&str, &str>) -> Result<T::M> {
63+
pub fn get_metric_with<S: BuildHasher>(&self, labels: &HashMap<&str, &str, S>) -> Result<T::M> {
6364
let h = self.hash_labels(labels)?;
6465

6566
if let Some(metric) = self.children.read().get(&h).cloned() {
@@ -81,7 +82,7 @@ impl<T: MetricVecBuilder> MetricVecCore<T> {
8182
Ok(())
8283
}
8384

84-
pub fn delete(&self, labels: &HashMap<&str, &str>) -> Result<()> {
85+
pub fn delete<S: BuildHasher>(&self, labels: &HashMap<&str, &str, S>) -> Result<()> {
8586
let h = self.hash_labels(labels)?;
8687

8788
let mut children = self.children.write();
@@ -113,7 +114,7 @@ impl<T: MetricVecBuilder> MetricVecCore<T> {
113114
Ok(h.finish())
114115
}
115116

116-
fn hash_labels(&self, labels: &HashMap<&str, &str>) -> Result<u64> {
117+
fn hash_labels<S: BuildHasher>(&self, labels: &HashMap<&str, &str, S>) -> Result<u64> {
117118
if labels.len() != self.desc.variable_labels.len() {
118119
return Err(Error::InconsistentCardinality {
119120
expect: self.desc.variable_labels.len(),
@@ -137,7 +138,7 @@ impl<T: MetricVecBuilder> MetricVecCore<T> {
137138
Ok(h.finish())
138139
}
139140

140-
fn get_label_values<'a>(&self, labels: &'a HashMap<&str, &str>) -> Result<Vec<&'a str>> {
141+
fn get_label_values<'a, S: BuildHasher>(&self, labels: &'a HashMap<&str, &str, S>) -> Result<Vec<&'a str>> {
141142
let mut values = Vec::new();
142143
for name in &self.desc.variable_labels {
143144
match labels.get(&name.as_ref()) {
@@ -188,7 +189,7 @@ impl<T: MetricVecBuilder> MetricVec<T> {
188189
pub fn create(metric_type: MetricType, new_metric: T, opts: T::P) -> Result<MetricVec<T>> {
189190
let desc = opts.describe()?;
190191
let v = MetricVecCore {
191-
children: RwLock::new(HashMap::new()),
192+
children: RwLock::new(HashMap::default()),
192193
desc,
193194
metric_type,
194195
new_metric,
@@ -237,7 +238,7 @@ impl<T: MetricVecBuilder> MetricVec<T> {
237238
/// This method is used for the same purpose as
238239
/// `get_metric_with_label_values`. See there for pros and cons of the two
239240
/// methods.
240-
pub fn get_metric_with(&self, labels: &HashMap<&str, &str>) -> Result<T::M> {
241+
pub fn get_metric_with<S: BuildHasher>(&self, labels: &HashMap<&str, &str, S>) -> Result<T::M> {
241242
self.v.get_metric_with(labels)
242243
}
243244

@@ -261,7 +262,7 @@ impl<T: MetricVecBuilder> MetricVec<T> {
261262
/// `with` works as `get_metric_with`, but panics if an error occurs. The method allows
262263
/// neat syntax like:
263264
/// httpReqs.with(Labels{"status":"404", "method":"POST"}).inc()
264-
pub fn with(&self, labels: &HashMap<&str, &str>) -> T::M {
265+
pub fn with<S: BuildHasher>(&self, labels: &HashMap<&str, &str, S>) -> T::M {
265266
self.get_metric_with(labels).unwrap()
266267
}
267268

@@ -289,7 +290,7 @@ impl<T: MetricVecBuilder> MetricVec<T> {
289290
///
290291
/// This method is used for the same purpose as `delete_label_values`. See
291292
/// there for pros and cons of the two methods.
292-
pub fn remove(&self, labels: &HashMap<&str, &str>) -> Result<()> {
293+
pub fn remove<S: BuildHasher>(&self, labels: &HashMap<&str, &str, S>) -> Result<()> {
293294
self.v.delete(labels)
294295
}
295296

0 commit comments

Comments
 (0)