Skip to content

Commit 8406cbc

Browse files
authored
subscriber: make Registry::enter/exit much faster (#1058) (#1059)
This backports #1058 to v0.1.x. This was already approved on master. ## Motivation We've tried very hard to make sure that entering and exiting spans is lightweight...in the `tracing-core` core machinery. Unfortunately, we haven't done any benchmarking of how subscriber implementations actually handle enter/exit events. It turns out that in `tracing-subscriber`'s `Registry`, there's actually significant overhead for entering a span: calling `span.enter()` may take as long as 69 ns (on my machine). ## Solution I've written some microbenchmarks for entering and exiting enabled spans using `tracing-subscriber::fmt`, comparing them with the overhead of calling `enter` on an enabled span. Based on this, I've made some performance improvements. These optimizations include: - Removing the `HashSet` that's used for tracking previously entered span IDs, in favor of linear search. Span stacks probably never deep enough for a hashmap to be faster than iterating over a couple of vec indices. - Preallocating the vec used for the span stack to hold at least 64 elements. This means we'll never have a lag spike from reallocating, as I think it'll almost never be deeper than 64 IDs. - Only cloning/dropping an ID's ref count for the _first_ ID in the stack. This makes entering and exiting enabled spans significantly faster: ![image](https://user-images.githubusercontent.com/2796466/96798681-3fc85000-13b6-11eb-9e85-7602d918ee09.png) It would be nice to continue optimizing this, but this might be about the best it gets given the other requirements that we're now making assertions about. Signed-off-by: Eliza Weisman <[email protected]>
1 parent 1aa9eff commit 8406cbc

File tree

4 files changed

+90
-27
lines changed

4 files changed

+90
-27
lines changed

tracing-subscriber/Cargo.toml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -84,3 +84,7 @@ harness = false
8484
[[bench]]
8585
name = "fmt"
8686
harness = false
87+
88+
[[bench]]
89+
name = "enter"
90+
harness = false

tracing-subscriber/benches/enter.rs

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
use criterion::{criterion_group, criterion_main, Criterion};
2+
use tracing_subscriber::prelude::*;
3+
4+
fn enter(c: &mut Criterion) {
5+
let mut group = c.benchmark_group("enter");
6+
let _subscriber = tracing_subscriber::fmt()
7+
.with_max_level(tracing::Level::INFO)
8+
.finish()
9+
.set_default();
10+
group.bench_function("enabled", |b| {
11+
let span = tracing::info_span!("foo");
12+
b.iter_with_large_drop(|| span.enter())
13+
});
14+
group.bench_function("disabled", |b| {
15+
let span = tracing::debug_span!("foo");
16+
b.iter_with_large_drop(|| span.enter())
17+
});
18+
}
19+
20+
fn enter_exit(c: &mut Criterion) {
21+
let mut group = c.benchmark_group("enter_exit");
22+
let _subscriber = tracing_subscriber::fmt()
23+
.with_max_level(tracing::Level::INFO)
24+
.finish()
25+
.set_default();
26+
group.bench_function("enabled", |b| {
27+
let span = tracing::info_span!("foo");
28+
b.iter(|| span.enter())
29+
});
30+
group.bench_function("disabled", |b| {
31+
let span = tracing::debug_span!("foo");
32+
b.iter(|| span.enter())
33+
});
34+
}
35+
36+
fn enter_many(c: &mut Criterion) {
37+
let mut group = c.benchmark_group("enter_many");
38+
let _subscriber = tracing_subscriber::fmt()
39+
.with_max_level(tracing::Level::INFO)
40+
.finish()
41+
.set_default();
42+
group.bench_function("enabled", |b| {
43+
let span1 = tracing::info_span!("span1");
44+
let _e1 = span1.enter();
45+
let span2 = tracing::info_span!("span2");
46+
let _e2 = span2.enter();
47+
let span3 = tracing::info_span!("span3");
48+
let _e3 = span3.enter();
49+
let span = tracing::info_span!("foo");
50+
b.iter_with_large_drop(|| span.enter())
51+
});
52+
group.bench_function("disabled", |b| {
53+
let span1 = tracing::info_span!("span1");
54+
let _e1 = span1.enter();
55+
let span2 = tracing::info_span!("span2");
56+
let _e2 = span2.enter();
57+
let span3 = tracing::info_span!("span3");
58+
let _e3 = span3.enter();
59+
let span = tracing::debug_span!("foo");
60+
b.iter_with_large_drop(|| span.enter())
61+
});
62+
}
63+
criterion_group!(benches, enter, enter_exit, enter_many);
64+
criterion_main!(benches);

tracing-subscriber/src/registry/sharded.rs

Lines changed: 10 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -202,19 +202,21 @@ impl Subscriber for Registry {
202202
fn event(&self, _: &Event<'_>) {}
203203

204204
fn enter(&self, id: &span::Id) {
205-
self.current_spans
205+
if self
206+
.current_spans
206207
.get_or_default()
207208
.borrow_mut()
208-
.push(self.clone_span(id));
209+
.push(id.clone())
210+
{
211+
self.clone_span(id);
212+
}
209213
}
210214

211215
fn exit(&self, id: &span::Id) {
212-
if let Some(id) = self
213-
.current_spans
214-
.get()
215-
.and_then(|spans| spans.borrow_mut().pop(id))
216-
{
217-
dispatcher::get_default(|dispatch| dispatch.try_close(id.clone()));
216+
if let Some(spans) = self.current_spans.get() {
217+
if spans.borrow_mut().pop(id) {
218+
dispatcher::get_default(|dispatch| dispatch.try_close(id.clone()));
219+
}
218220
}
219221
}
220222

tracing-subscriber/src/registry/stack.rs

Lines changed: 12 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,3 @@
1-
use std::collections::HashSet;
2-
31
pub(crate) use tracing_core::span::Id;
42

53
#[derive(Debug)]
@@ -15,34 +13,29 @@ struct ContextId {
1513
#[derive(Debug, Default)]
1614
pub(crate) struct SpanStack {
1715
stack: Vec<ContextId>,
18-
ids: HashSet<Id>,
1916
}
2017

2118
impl SpanStack {
22-
pub(crate) fn push(&mut self, id: Id) {
23-
let duplicate = self.ids.contains(&id);
24-
if !duplicate {
25-
self.ids.insert(id.clone());
26-
}
27-
self.stack.push(ContextId { id, duplicate })
19+
#[inline]
20+
pub(crate) fn push(&mut self, id: Id) -> bool {
21+
let duplicate = self.stack.iter().any(|i| i.id == id);
22+
self.stack.push(ContextId { id, duplicate });
23+
!duplicate
2824
}
2925

30-
pub(crate) fn pop(&mut self, expected_id: &Id) -> Option<Id> {
26+
#[inline]
27+
pub(crate) fn pop(&mut self, expected_id: &Id) -> bool {
3128
if let Some((idx, _)) = self
3229
.stack
3330
.iter()
3431
.enumerate()
3532
.rev()
3633
.find(|(_, ctx_id)| ctx_id.id == *expected_id)
3734
{
38-
let ContextId { id, duplicate } = self.stack.remove(idx);
39-
if !duplicate {
40-
self.ids.remove(&id);
41-
}
42-
Some(id)
43-
} else {
44-
None
35+
let ContextId { id: _, duplicate } = self.stack.remove(idx);
36+
return !duplicate;
4537
}
38+
false
4639
}
4740

4841
#[inline]
@@ -65,7 +58,7 @@ mod tests {
6558
let id = Id::from_u64(1);
6659
stack.push(id.clone());
6760

68-
assert_eq!(Some(id.clone()), stack.pop(&id));
61+
assert!(stack.pop(&id));
6962
}
7063

7164
#[test]
@@ -75,6 +68,6 @@ mod tests {
7568
stack.push(Id::from_u64(2));
7669

7770
let id = Id::from_u64(1);
78-
assert_eq!(Some(id.clone()), stack.pop(&id));
71+
assert!(stack.pop(&id));
7972
}
8073
}

0 commit comments

Comments
 (0)