use std::{
fmt,
time::{Duration, Instant},
};
#[derive(Debug)]
pub struct Measure {
name: &'static str,
start: Instant,
duration: u64,
}
fn duration_as_ns(d: &Duration) -> u64 {
d.as_secs()
.saturating_mul(1_000_000_000)
.saturating_add(u64::from(d.subsec_nanos()))
}
#[cfg(target_arch = "x86_64")]
mod x86 {
use std::sync::Once;
static INIT: Once = Once::new();
static mut DUR_TEST: u64 = 0;
static mut TICKS_TEST: u64 = 1;
static mut TICKS_OVERHEAD: u64 = 1_000_000;
use super::duration_as_ns;
use std::fmt;
use std::time::{Duration, Instant};
use crate::x86::{check_rdtscp, rdtscp};
fn calibration() {
if !check_rdtscp() {
panic!("No RDTSCP support!");
}
for _i in 0..100 {
let start = rdtscp();
let stop = rdtscp();
let diff = stop - start;
unsafe {
if TICKS_OVERHEAD > diff {
TICKS_OVERHEAD = diff
}
}
}
let t_start = Instant::now();
let start = rdtscp();
std::thread::sleep(Duration::from_millis(100));
let stop = rdtscp();
unsafe {
DUR_TEST = duration_as_ns(&t_start.elapsed());
TICKS_TEST = stop - start;
}
}
#[allow(dead_code)]
pub fn tsc_status() -> String {
INIT.call_once(|| {
calibration();
});
let ticks_us: f32 = unsafe { TICKS_TEST as f32 / DUR_TEST as f32 };
format!("rdtscp ticks {:.3} per ns", ticks_us)
}
pub struct MeasureTsc {
name: &'static str,
start: u64,
duration: u64,
ticks: u64,
}
impl MeasureTsc {
pub fn start(name: &'static str) -> Self {
INIT.call_once(|| {
calibration();
});
assert!(check_rdtscp(), "rdtscp NOT SUPPORTED!");
Self {
name,
start: rdtscp(),
duration: 0,
ticks: 0,
}
}
pub fn stop(&mut self) {
self.ticks = unsafe { rdtscp() - self.start - TICKS_OVERHEAD };
self.duration = unsafe { self.ticks.saturating_mul(DUR_TEST) / TICKS_TEST }
}
pub fn as_ticks(&self) -> u64 {
self.ticks
}
pub fn as_ns(&self) -> u64 {
self.duration
}
pub fn as_us(&self) -> u64 {
self.duration / 1000
}
pub fn as_ms(&self) -> u64 {
self.duration / (1000 * 1000)
}
pub fn as_s(&self) -> f64 {
self.duration as f64 * 0.000_000_001f64
}
}
impl fmt::Display for MeasureTsc {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
if self.duration == 0 {
write!(f, "TSC {} running", self.name)
} else if self.as_us() < 1 {
write!(f, "TSC {} took {}ns", self.name, self.duration)
} else if self.as_ms() < 1 {
write!(f, "TSC {} took {}us", self.name, self.as_us())
} else {
write!(f, "TSC {} took {}ms", self.name, self.as_ms())
}
}
}
}
#[cfg(target_arch = "x86_64")]
pub use x86::MeasureTsc;
impl Measure {
pub fn start(name: &'static str) -> Self {
Self {
name,
start: Instant::now(),
duration: 0,
}
}
pub fn stop(&mut self) {
self.duration = duration_as_ns(&self.start.elapsed());
}
pub fn as_ns(&self) -> u64 {
self.duration
}
pub fn as_us(&self) -> u64 {
self.duration / 1000
}
pub fn as_ms(&self) -> u64 {
self.duration / (1000 * 1000)
}
pub fn as_s(&self) -> f64 {
self.duration as f64 * 0.000_000_001f64
}
pub fn this<T, R, F: FnOnce(T) -> R>(func: F, args: T, name: &'static str) -> (R, Self) {
let mut measure = Self::start(name);
let result = func(args);
measure.stop();
(result, measure)
}
}
impl fmt::Display for Measure {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
if self.duration == 0 {
write!(f, "{} running", self.name)
} else if self.as_us() < 1 {
write!(f, "{} took {}ns", self.name, self.duration)
} else if self.as_ms() < 1 {
write!(f, "{} took {}us", self.name, self.as_us())
} else if self.as_s() < 1. {
write!(f, "{} took {}ms", self.name, self.as_ms())
} else {
write!(f, "{} took {:.1}s", self.name, self.as_s())
}
}
}
#[cfg(test)]
mod tests {
use {
super::*,
std::{thread::sleep, time::Duration},
};
#[test]
fn test_measure() {
let mut measure = Measure::start("test");
sleep(Duration::from_secs(1));
measure.stop();
assert!(measure.as_s() >= 0.99f64 && measure.as_s() <= 1.01f64);
assert!(measure.as_ms() >= 990 && measure.as_ms() <= 1_010);
assert!(measure.as_us() >= 999_000 && measure.as_us() <= 1_010_000);
}
#[test]
#[cfg(target_arch = "x86_64")]
fn test_measure_tsc() {
use super::{x86::tsc_status, MeasureTsc};
use crate::x86;
if x86::check_rdtscp() {
println!("tsc status: {}", tsc_status());
} else {
println!("NO rdtscp SUPPORT");
return;
}
let mut measure = MeasureTsc::start("test");
sleep(Duration::from_secs(1));
measure.stop();
assert!(measure.as_ms() >= 990 && measure.as_ms() <= 1_010);
assert!(measure.as_us() >= 999_000 && measure.as_us() <= 1_010_000);
}
#[test]
fn test_measure_display() {
let measure = Measure {
name: "test_ns",
start: Instant::now(),
duration: 1,
};
assert_eq!(format!("{}", measure), "test_ns took 1ns");
let measure = Measure {
name: "test_us",
start: Instant::now(),
duration: 1000,
};
assert_eq!(format!("{}", measure), "test_us took 1us");
let measure = Measure {
name: "test_ms",
start: Instant::now(),
duration: 1000 * 1000,
};
assert_eq!(format!("{}", measure), "test_ms took 1ms");
let measure = Measure {
name: "test_s",
start: Instant::now(),
duration: 1000 * 1000 * 1000,
};
assert_eq!(format!("{}", measure), "test_s took 1.0s");
let measure = Measure::start("test_not_stopped");
assert_eq!(format!("{}", measure), "test_not_stopped running");
}
fn my_multiply(x: i32, y: i32) -> i32 {
x * y
}
fn my_multiply_tuple(args: (i32, i32)) -> i32 {
let (x, y) = args;
my_multiply(x, y)
}
fn square(x: i32) -> i32 {
my_multiply(x, x)
}
struct SomeStruct {
x: i32,
}
impl SomeStruct {
fn add_to(&self, x: i32) -> i32 {
x + self.x
}
}
#[test]
fn test_measure_this() {
{
let (_result, measure) = Measure::this(|s| sleep(Duration::from_secs(s)), 1, "test");
assert!(measure.as_s() >= 0.99f64 && measure.as_s() <= 1.01f64);
assert!(measure.as_ms() >= 990 && measure.as_ms() <= 1_010);
assert!(measure.as_us() >= 999_000 && measure.as_us() <= 1_010_000);
}
{
let expected = 1;
let (actual, _measure) = Measure::this(|x| x, expected, "test");
assert_eq!(actual, expected);
}
{
let (result, _measure) = Measure::this(|(x, y)| x + y, (1, 2), "test");
assert_eq!(result, 1 + 2);
}
{
let (result, _measure) = Measure::this(|(x, y)| my_multiply(x, y), (3, 4), "test");
assert_eq!(result, 3 * 4);
}
{
let (result, _measure) = Measure::this(square, 5, "test");
assert_eq!(result, 5 * 5)
}
{
let (result, _measure) = Measure::this(my_multiply_tuple, (3, 4), "test");
assert_eq!(result, 3 * 4);
}
{
let some_struct = SomeStruct { x: 42 };
let (result, _measure) = Measure::this(
|(obj, x)| SomeStruct::add_to(&obj, x),
(some_struct, 4),
"test",
);
assert_eq!(result, 42 + 4);
}
{
let some_struct = SomeStruct { x: 42 };
let (result, _measure) = Measure::this(
|(obj, x)| SomeStruct::add_to(obj, x),
(&some_struct, 4),
"test",
);
assert_eq!(result, 42 + 4);
assert_eq!(some_struct.add_to(6), 42 + 6);
}
}
}