Skip to content

Commit 6682594

Browse files
authored
Introduce key/value map bench (#1121)
1 parent 6930e87 commit 6682594

File tree

4 files changed

+325
-45
lines changed

4 files changed

+325
-45
lines changed

opentelemetry-sdk/Cargo.toml

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@ all-features = true
3636
rustdoc-args = ["--cfg", "docsrs"]
3737

3838
[dev-dependencies]
39+
indexmap = "1.8"
3940
criterion = { version = "0.4.0", features = ["html_reports"] }
4041
pprof = { version = "0.11.1", features = ["flamegraph", "criterion"] }
4142

@@ -50,6 +51,14 @@ rt-tokio = ["tokio", "tokio-stream"]
5051
rt-tokio-current-thread = ["tokio", "tokio-stream"]
5152
rt-async-std = ["async-std"]
5253

54+
[[bench]]
55+
name = "key_value_map"
56+
harness = false
57+
58+
[[bench]]
59+
name = "span_builder"
60+
harness = false
61+
5362
[[bench]]
5463
name = "trace"
5564
harness = false
Lines changed: 208 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,208 @@
1+
use criterion::{
2+
black_box, criterion_group, criterion_main, BatchSize::SmallInput, BenchmarkId, Criterion,
3+
};
4+
use indexmap::IndexMap;
5+
use opentelemetry_api::{Key, KeyValue, Value};
6+
use opentelemetry_sdk::trace::EvictedHashMap;
7+
use pprof::criterion::{Output, PProfProfiler};
8+
use std::iter::Iterator;
9+
10+
fn criterion_benchmark(c: &mut Criterion) {
11+
let cap = 32;
12+
let input = [(2, 32, cap), (8, 32, cap), (32, 32, cap)];
13+
populate_benchmark(c, &input);
14+
lookup_benchmark(c, &input);
15+
populate_and_lookup_benchmark(c, &input);
16+
}
17+
18+
fn populate_benchmark(c: &mut Criterion, input: &[(usize, u32, usize)]) {
19+
let mut group = c.benchmark_group("populate");
20+
for &(n, max, capacity) in input {
21+
let parameter_string = format!("{n:02}/{max:02}/{capacity:02}");
22+
23+
group.bench_function(
24+
BenchmarkId::new("EvictedHashMap", parameter_string.clone()),
25+
|b| {
26+
b.iter(|| populate_evicted_hashmap(n, max, capacity));
27+
},
28+
);
29+
group.bench_function(
30+
BenchmarkId::new("IndexMap", parameter_string.clone()),
31+
|b| {
32+
b.iter(|| populate_indexmap(n, max, capacity));
33+
},
34+
);
35+
group.bench_function(BenchmarkId::new("TwoVecs", parameter_string.clone()), |b| {
36+
b.iter(|| populate_twovecs(n, max, capacity));
37+
});
38+
group.bench_function(BenchmarkId::new("OneVec", parameter_string.clone()), |b| {
39+
b.iter(|| populate_onevec(n, max, capacity));
40+
});
41+
}
42+
group.finish();
43+
}
44+
45+
fn lookup_benchmark(c: &mut Criterion, input: &[(usize, u32, usize)]) {
46+
let mut group = c.benchmark_group("lookup");
47+
for &(n, max, capacity) in input {
48+
let lookup_keys = &MAP_KEYS[n - 2..n];
49+
let parameter_string = format!("{n:02}/{max:02}/{capacity:02}");
50+
group.bench_function(
51+
BenchmarkId::new("EvictedHashMap", parameter_string.clone()),
52+
|b| {
53+
b.iter_batched(
54+
|| populate_evicted_hashmap(n, max, capacity),
55+
|map| lookup_evicted_hashmap(&map, lookup_keys),
56+
SmallInput,
57+
);
58+
},
59+
);
60+
group.bench_function(
61+
BenchmarkId::new("IndexMap", parameter_string.clone()),
62+
|b| {
63+
b.iter_batched(
64+
|| populate_indexmap(n, max, capacity),
65+
|map| lookup_indexmap(&map, lookup_keys),
66+
SmallInput,
67+
);
68+
},
69+
);
70+
group.bench_function(BenchmarkId::new("OneVec", parameter_string.clone()), |b| {
71+
b.iter_batched(
72+
|| populate_onevec(n, max, capacity),
73+
|vec| lookup_onevec(&vec, lookup_keys),
74+
SmallInput,
75+
);
76+
});
77+
group.bench_function(BenchmarkId::new("TwoVecs", parameter_string.clone()), |b| {
78+
b.iter_batched(
79+
|| populate_twovecs(n, max, capacity),
80+
|(keys, vals)| lookup_twovec(&keys, &vals, lookup_keys),
81+
SmallInput,
82+
);
83+
});
84+
}
85+
group.finish();
86+
}
87+
88+
fn populate_and_lookup_benchmark(c: &mut Criterion, input: &[(usize, u32, usize)]) {
89+
let mut group = c.benchmark_group("populate_and_lookup");
90+
for &(n, max, capacity) in input {
91+
let lookup_keys = &MAP_KEYS[n - 2..n];
92+
let parameter_string = format!("{n:02}/{max:02}/{capacity:02}");
93+
group.bench_function(
94+
BenchmarkId::new("EvictedHashMap", parameter_string.clone()),
95+
|b| {
96+
b.iter(|| {
97+
let map = populate_evicted_hashmap(n, max, capacity);
98+
lookup_evicted_hashmap(&map, lookup_keys);
99+
});
100+
},
101+
);
102+
group.bench_function(
103+
BenchmarkId::new("IndexMap", parameter_string.clone()),
104+
|b| {
105+
b.iter(|| {
106+
let map = populate_indexmap(n, max, capacity);
107+
lookup_indexmap(&map, lookup_keys);
108+
});
109+
},
110+
);
111+
group.bench_function(BenchmarkId::new("OneVec", parameter_string.clone()), |b| {
112+
b.iter(|| {
113+
let vec = populate_onevec(n, max, capacity);
114+
lookup_onevec(&vec, lookup_keys);
115+
});
116+
});
117+
group.bench_function(BenchmarkId::new("TwoVecs", parameter_string.clone()), |b| {
118+
b.iter(|| {
119+
let (keys, vals) = populate_twovecs(n, max, capacity);
120+
lookup_twovec(&keys, &vals, lookup_keys);
121+
});
122+
});
123+
}
124+
group.finish();
125+
}
126+
127+
fn populate_evicted_hashmap(n: usize, max: u32, capacity: usize) -> EvictedHashMap {
128+
let mut map = EvictedHashMap::new(max, capacity);
129+
for (idx, key) in MAP_KEYS.iter().enumerate().take(n) {
130+
map.insert(KeyValue::new(*key, idx as i64));
131+
}
132+
map
133+
}
134+
135+
fn lookup_evicted_hashmap(map: &EvictedHashMap, keys: &[&'static str]) {
136+
for key in keys {
137+
black_box(map.get(&Key::new(*key)));
138+
}
139+
}
140+
141+
fn populate_indexmap(n: usize, max: u32, _capacity: usize) -> IndexMap<Key, Value> {
142+
let mut map = IndexMap::with_capacity(max as usize);
143+
for (idx, key) in MAP_KEYS.iter().enumerate().take(n) {
144+
map.insert(Key::new(*key), Value::I64(idx as i64));
145+
}
146+
map
147+
}
148+
149+
fn lookup_indexmap(map: &IndexMap<Key, Value>, keys: &[&'static str]) {
150+
for key in keys {
151+
black_box(map.get(&Key::new(*key)));
152+
}
153+
}
154+
155+
fn populate_onevec(n: usize, max: u32, _capacity: usize) -> Vec<(Key, Value)> {
156+
let mut tuples = Vec::with_capacity(max as usize);
157+
for (idx, key) in MAP_KEYS.iter().enumerate().take(n) {
158+
tuples.push((Key::new(*key), Value::I64(idx as i64)));
159+
}
160+
tuples
161+
}
162+
163+
fn lookup_onevec(vec: &[(Key, Value)], keys: &[&'static str]) {
164+
for key in keys {
165+
black_box(
166+
vec.iter()
167+
.position(|(k, _v)| *k == Key::new(*key))
168+
.map(|idx| vec.get(idx)),
169+
);
170+
}
171+
}
172+
173+
fn populate_twovecs(n: usize, max: u32, _capacity: usize) -> (Vec<Key>, Vec<Value>) {
174+
let mut keys = Vec::with_capacity(max as usize);
175+
let mut vals = Vec::with_capacity(max as usize);
176+
for (idx, key) in MAP_KEYS.iter().enumerate().take(n) {
177+
keys.push(Key::new(*key));
178+
vals.push(Value::I64(idx as i64));
179+
}
180+
(keys, vals)
181+
}
182+
183+
fn lookup_twovec(keys: &[Key], vals: &[Value], lookup_keys: &[&'static str]) {
184+
for key in lookup_keys {
185+
black_box(
186+
keys.iter()
187+
.position(|k| *k == Key::new(*key))
188+
.map(|idx| vals.get(idx)),
189+
);
190+
}
191+
}
192+
193+
const MAP_KEYS: [&str; 64] = [
194+
"key.1", "key.2", "key.3", "key.4", "key.5", "key.6", "key.7", "key.8", "key.9", "key.10",
195+
"key.11", "key.12", "key.13", "key.14", "key.15", "key.16", "key.17", "key.18", "key.19",
196+
"key.20", "key.21", "key.22", "key.23", "key.24", "key.25", "key.26", "key.27", "key.28",
197+
"key.29", "key.30", "key.31", "key.32", "key.33", "key.34", "key.35", "key.36", "key.37",
198+
"key.38", "key.39", "key.40", "key.41", "key.42", "key.43", "key.44", "key.45", "key.46",
199+
"key.47", "key.48", "key.49", "key.50", "key.51", "key.52", "key.53", "key.54", "key.55",
200+
"key.56", "key.57", "key.58", "key.59", "key.60", "key.61", "key.62", "key.63", "key.64",
201+
];
202+
203+
criterion_group! {
204+
name = benches;
205+
config = Criterion::default().with_profiler(PProfProfiler::new(100, Output::Flamegraph(None)));
206+
targets = criterion_benchmark
207+
}
208+
criterion_main!(benches);
Lines changed: 107 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,107 @@
1+
use criterion::{criterion_group, criterion_main, BenchmarkId, Criterion};
2+
use futures_util::future::BoxFuture;
3+
use opentelemetry_api::{
4+
trace::{OrderMap, Span, Tracer, TracerProvider},
5+
KeyValue,
6+
};
7+
use opentelemetry_sdk::{
8+
export::trace::{ExportResult, SpanData, SpanExporter},
9+
trace as sdktrace,
10+
};
11+
use pprof::criterion::{Output, PProfProfiler};
12+
13+
fn criterion_benchmark(c: &mut Criterion) {
14+
span_builder_benchmark_group(c)
15+
}
16+
17+
fn span_builder_benchmark_group(c: &mut Criterion) {
18+
let mut group = c.benchmark_group("span_builder");
19+
group.bench_function("simplest", |b| {
20+
let (_provider, tracer) = not_sampled_provider();
21+
b.iter(|| {
22+
let mut span = tracer.span_builder("span").start(&tracer);
23+
span.end();
24+
})
25+
});
26+
group.bench_function(BenchmarkId::new("with_attributes", "1"), |b| {
27+
let (_provider, tracer) = not_sampled_provider();
28+
b.iter(|| {
29+
let mut span = tracer
30+
.span_builder("span")
31+
.with_attributes([KeyValue::new(MAP_KEYS[0], "value")])
32+
.start(&tracer);
33+
span.end();
34+
})
35+
});
36+
group.bench_function(BenchmarkId::new("with_attributes", "4"), |b| {
37+
let (_provider, tracer) = not_sampled_provider();
38+
b.iter(|| {
39+
let mut span = tracer
40+
.span_builder("span")
41+
.with_attributes([
42+
KeyValue::new(MAP_KEYS[0], "value"),
43+
KeyValue::new(MAP_KEYS[1], "value"),
44+
KeyValue::new(MAP_KEYS[2], "value"),
45+
KeyValue::new(MAP_KEYS[3], "value"),
46+
])
47+
.start(&tracer);
48+
span.end();
49+
})
50+
});
51+
group.bench_function(BenchmarkId::new("with_attributes_map", "1"), |b| {
52+
let (_provider, tracer) = not_sampled_provider();
53+
b.iter(|| {
54+
let mut span = tracer
55+
.span_builder("span")
56+
.with_attributes_map(OrderMap::from_iter([KeyValue::new(MAP_KEYS[0], "value")]))
57+
.start(&tracer);
58+
span.end();
59+
})
60+
});
61+
group.bench_function(BenchmarkId::new("with_attributes_map", "4"), |b| {
62+
let (_provider, tracer) = not_sampled_provider();
63+
b.iter(|| {
64+
let mut span = tracer
65+
.span_builder("span")
66+
.with_attributes_map(OrderMap::from_iter([KeyValue::new(MAP_KEYS[0], "value")]))
67+
.start(&tracer);
68+
span.end();
69+
})
70+
});
71+
group.finish();
72+
}
73+
74+
fn not_sampled_provider() -> (sdktrace::TracerProvider, sdktrace::Tracer) {
75+
let provider = sdktrace::TracerProvider::builder()
76+
.with_config(sdktrace::config().with_sampler(sdktrace::Sampler::AlwaysOff))
77+
.with_simple_exporter(NoopExporter)
78+
.build();
79+
let tracer = provider.tracer("not-sampled");
80+
(provider, tracer)
81+
}
82+
83+
#[derive(Debug)]
84+
struct NoopExporter;
85+
86+
impl SpanExporter for NoopExporter {
87+
fn export(&mut self, _spans: Vec<SpanData>) -> BoxFuture<'static, ExportResult> {
88+
Box::pin(futures_util::future::ready(Ok(())))
89+
}
90+
}
91+
92+
const MAP_KEYS: [&str; 64] = [
93+
"key.1", "key.2", "key.3", "key.4", "key.5", "key.6", "key.7", "key.8", "key.9", "key.10",
94+
"key.11", "key.12", "key.13", "key.14", "key.15", "key.16", "key.17", "key.18", "key.19",
95+
"key.20", "key.21", "key.22", "key.23", "key.24", "key.25", "key.26", "key.27", "key.28",
96+
"key.29", "key.30", "key.31", "key.32", "key.33", "key.34", "key.35", "key.36", "key.37",
97+
"key.38", "key.39", "key.40", "key.41", "key.42", "key.43", "key.44", "key.45", "key.46",
98+
"key.47", "key.48", "key.49", "key.50", "key.51", "key.52", "key.53", "key.54", "key.55",
99+
"key.56", "key.57", "key.58", "key.59", "key.60", "key.61", "key.62", "key.63", "key.64",
100+
];
101+
102+
criterion_group! {
103+
name = benches;
104+
config = Criterion::default().with_profiler(PProfProfiler::new(100, Output::Flamegraph(None)));
105+
targets = criterion_benchmark
106+
}
107+
criterion_main!(benches);

0 commit comments

Comments
 (0)