Skip to content

Commit

Permalink
Use fastest thread-local impl
Browse files Browse the repository at this point in the history
Benchmarking code:

```
use std::cell::{Cell, RefCell, UnsafeCell};
use std::env::args_os;
use std::ffi::OsStr;

fn a() {
    thread_local! {
        static TLS: RefCell<Vec<usize>> = const { RefCell::new(Vec::new()) };
    }

    for i in 0..10000 {
        TLS.with(|s| {
            s.borrow_mut().push(i);
        });
    }
    TLS.with(|s| s.borrow_mut().clear());
}

fn b() {
    thread_local! {
        static TLS: RefCell<Vec<usize>> = const { RefCell::new(Vec::new()) };
    }

    for i in 0..10000 {
        TLS.with(|s| {
            unsafe { s.try_borrow_mut().unwrap_unchecked() }.push(i);
        });
    }
    TLS.with(|s| unsafe { s.try_borrow_mut().unwrap_unchecked() }.clear());
}

fn c() {
    thread_local! {
        static TLS: Cell<Vec<usize>> = const { Cell::new(Vec::new()) };
    }

    for i in 0..10000 {
        TLS.with(|s| {
            let mut v = s.take();
            v.push(i);
            s.set(v);
        });
    }
    TLS.with(|s| {
        let mut v = s.take();
        v.clear();
        s.set(v);
    });
}

fn d() {
    thread_local! {
        static TLS: UnsafeCell<Vec<usize>> = const { UnsafeCell::new(Vec::new()) };
    }

    for i in 0..10000 {
        TLS.with(|s| {
            unsafe { &mut *s.get() }.push(i);
        });
    }
    TLS.with(|s| unsafe { &mut *s.get() }.clear());
}

fn e() {
    thread_local! {
        static TLS: UnsafeCell<Vec<usize>> = UnsafeCell::new(Vec::new());
    }

    for i in 0..10000 {
        TLS.with(|s| {
            unsafe { &mut *s.get() }.push(i);
        });
    }
    TLS.with(|s| unsafe { &mut *s.get() }.clear());
}

fn f() {
    thread_local! {
        static TLS: RefCell<Vec<usize>> = RefCell::new(Vec::new());
    }

    for i in 0..10000 {
        TLS.with(|s| {
            s.borrow_mut().push(i);
        });
    }
    TLS.with(|s| s.borrow_mut().clear());
}

fn main() {
    let cmd = args_os().nth(1).unwrap();
    for _ in 0..1000 {
        if cmd == OsStr::new("refcell") {
            a();
        } else if cmd == OsStr::new("refcell_unsafe") {
            b();
        } else if cmd == OsStr::new("cell") {
            c();
        } else if cmd == OsStr::new("unsafecell") {
            d();
        } else if cmd == OsStr::new("unsafecell_non_const") {
            e();
        } else if cmd == OsStr::new("refcell_non_const") {
            f();
        } else {
            panic!();
        }
    }
}
```

Benchmarks:

```
> hyperfine -N "./foo refcell" "./foo refcell_unsafe" "./foo cell" "./foo unsafecell" "./foo unsafecell_non_const" "./foo refcell_non_const"
Benchmark 1: ./foo refcell
  Time (mean ± σ):      17.2 ms ±   0.6 ms    [User: 16.8 ms, System: 0.3 ms]
  Range (min … max):    15.0 ms …  21.3 ms    143 runs

Benchmark 2: ./foo refcell_unsafe
  Time (mean ± σ):      18.1 ms ±   0.3 ms    [User: 17.7 ms, System: 0.3 ms]
  Range (min … max):    16.6 ms …  21.0 ms    167 runs

  Warning: Statistical outliers were detected. Consider re-running this benchmark on a quiet system without any interferences from other programs. It might help to use the '--warmup' or '--prepare' options.

Benchmark 3: ./foo cell
  Time (mean ± σ):      36.0 ms ±   0.9 ms    [User: 35.9 ms, System: 0.1 ms]
  Range (min … max):    34.3 ms …  42.7 ms    81 runs

  Warning: Statistical outliers were detected. Consider re-running this benchmark on a quiet system without any interferences from other programs. It might help to use the '--warmup' or '--prepare' options.

Benchmark 4: ./foo unsafecell
  Time (mean ± σ):      15.2 ms ±   1.1 ms    [User: 14.7 ms, System: 0.3 ms]
  Range (min … max):    14.2 ms …  19.2 ms    210 runs

Benchmark 5: ./foo unsafecell_non_const
  Time (mean ± σ):      15.5 ms ±   0.3 ms    [User: 15.0 ms, System: 0.4 ms]
  Range (min … max):    15.0 ms …  16.3 ms    193 runs

Benchmark 6: ./foo refcell_non_const
  Time (mean ± σ):      25.9 ms ±   5.3 ms    [User: 25.5 ms, System: 0.3 ms]
  Range (min … max):    17.9 ms …  42.2 ms    146 runs

Summary
  './foo unsafecell' ran
    1.02 ± 0.08 times faster than './foo unsafecell_non_const'
    1.13 ± 0.09 times faster than './foo refcell'
    1.19 ± 0.09 times faster than './foo refcell_unsafe'
    1.71 ± 0.37 times faster than './foo refcell_non_const'
    2.37 ± 0.18 times faster than './foo cell'
```

Signed-off-by: Alex Saveau <saveau.alexandre@gmail.com>
  • Loading branch information
SUPERCILEX committed Dec 26, 2023
1 parent f6c90de commit 9017396
Showing 1 changed file with 75 additions and 59 deletions.
134 changes: 75 additions & 59 deletions tracing-tracy/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -49,8 +49,12 @@
#![doc = include_str!("../FEATURES.mkd")]
#![cfg_attr(tracing_tracy_docs, feature(doc_auto_cfg))]

use std::cell::Cell;
use std::{borrow::Cow, cell::RefCell, collections::VecDeque, fmt::Write};
use std::{
borrow::Cow,
cell::{RefCell, UnsafeCell},
collections::VecDeque,
fmt::Write,
};
use tracing_core::{
field::{Field, Visit},
span::{Attributes, Id, Record},
Expand All @@ -69,8 +73,8 @@ pub use client;

thread_local! {
/// A stack of spans currently active on the current thread.
static TRACY_SPAN_STACK: RefCell<VecDeque<(Span, u64)>> =
RefCell::new(VecDeque::with_capacity(16));
static TRACY_SPAN_STACK: UnsafeCell<VecDeque<(Span, u64)>> =
const { UnsafeCell::new(VecDeque::new()) };
}

/// A tracing layer that collects data in Tracy profiling format.
Expand Down Expand Up @@ -179,55 +183,63 @@ where

fn on_event(&self, event: &Event, _: Context<'_, S>) {
thread_local! {
static BUF: Cell<String> = Cell::new(String::new());
static BUF: RefCell<String> = const { RefCell::new(String::new()) };
}

let mut visitor = TracyEventFieldVisitor {
dest: BUF.take(),
first: true,
frame_mark: false,
};
BUF.with(|buf| {
// Formatting fields can execute arbitrary user code which includes potentially calling
// on_event again. Thus, we must fallback to dynamic allocation when that happens.
let mut buf = buf.try_borrow_mut();
let mut backup = String::new();
let mut visitor = TracyEventFieldVisitor {
dest: buf.as_deref_mut().unwrap_or(&mut backup),
first: true,
frame_mark: false,
};

event.record(&mut visitor);
if !visitor.first {
self.client.message(
self.truncate_to_length(
&visitor.dest,
"",
"",
"event message is too long and was truncated",
),
self.stack_depth,
);
}
if visitor.frame_mark {
self.client.frame_mark();
}
event.record(&mut visitor);
if !visitor.first {
self.client.message(
self.truncate_to_length(
visitor.dest,
"",
"",
"event message is too long and was truncated",
),
self.stack_depth,
);
}
if visitor.frame_mark {
self.client.frame_mark();
}

visitor.dest.clear();
BUF.set(visitor.dest)
if let Ok(mut buf) = buf {
buf.clear()
}
});
}

fn on_enter(&self, id: &Id, ctx: Context<S>) {
let Some(span_data) = ctx.span(id) else {
return;
};

let metadata = span_data.metadata();
let file = metadata.file().unwrap_or("<not available>");
let line = metadata.line().unwrap_or(0);
let name: Cow<str> =
if let Some(fields) = span_data.extensions().get::<FormattedFields<F>>() {
if fields.fields.is_empty() {
metadata.name().into()
let stack_frame = {
let metadata = span_data.metadata();
let name: Cow<str> =
if let Some(fields) = span_data.extensions().get::<FormattedFields<F>>() {
if fields.fields.is_empty() {
metadata.name().into()
} else {
format!("{}{{{}}}", metadata.name(), fields.fields.as_str()).into()
}
} else {
format!("{}{{{}}}", metadata.name(), fields.fields.as_str()).into()
}
} else {
metadata.name().into()
};
TRACY_SPAN_STACK.with(|s| {
s.borrow_mut().push_back((
metadata.name().into()
};

let file = metadata.file().unwrap_or("<not available>");
let line = metadata.line().unwrap_or(0);
(
self.client.clone().span_alloc(
Some(self.truncate_to_length(
&name,
Expand All @@ -241,40 +253,44 @@ where
self.stack_depth,
),
id.into_u64(),
));
)
};

TRACY_SPAN_STACK.with(|s| {
unsafe { &mut *s.get() }.push_back(stack_frame);
});
}

fn on_exit(&self, id: &Id, _: Context<S>) {
TRACY_SPAN_STACK.with(|s| {
if let Some((span, span_id)) = s.borrow_mut().pop_back() {
if id.into_u64() != span_id {
self.client.color_message(
"Tracing spans exited out of order! \
Trace may not be accurate for this span stack.",
0xFF000000,
self.stack_depth,
);
}
drop(span);
} else {
let stack_frame = TRACY_SPAN_STACK.with(|s| unsafe { &mut *s.get() }.pop_back());

if let Some((span, span_id)) = stack_frame {
if id.into_u64() != span_id {
self.client.color_message(
"Exiting a tracing span, but got nothing on the tracy span stack!",
"Tracing spans exited out of order! \
Trace may not be accurate for this span stack.",
0xFF000000,
self.stack_depth,
);
}
});
drop(span);
} else {
self.client.color_message(
"Exiting a tracing span, but got nothing on the tracy span stack!",
0xFF000000,
self.stack_depth,
);
}
}
}

struct TracyEventFieldVisitor {
dest: String,
struct TracyEventFieldVisitor<'a> {
dest: &'a mut String,
frame_mark: bool,
first: bool,
}

impl Visit for TracyEventFieldVisitor {
impl Visit for TracyEventFieldVisitor<'_> {
fn record_bool(&mut self, field: &Field, value: bool) {
match (value, field.name()) {
(true, "tracy.frame_mark") => self.frame_mark = true,
Expand Down

0 comments on commit 9017396

Please sign in to comment.