-
Notifications
You must be signed in to change notification settings - Fork 34
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Does it make sense to pin Upkeep
thread?
#92
Comments
I think, in practice, though, there's not likely to be a meaningful difference. The majority of error when using So for small core-to-core TSC offset errors, they will likely be drowned out by the inherent error bounds of the recent time feature itself. If you have cores with just wildly wrong TSC offsets (which is definitely real, and happened in #61 as you've seen), then you'll end up being backstopped by the monotonic logic that prevents negative durations when comparing two I'm not against pinning the upkeep thread in principle, although it feels like it might introduce a lot of extra code to do so since I don't believe there's a way in stdlib to pin threads in a cross-platform way. If this is something you're interested in trying to work up a PR for, I'll say that I'm willing to review it but I can't promise that I would accept it. |
Thank you for a detailed response! I was afraid that perhaps there's protection from getting negative values, but no protection from getting "too positive" values. That is, getting a I've decided to do some experimentation and got really surprising results. The proof of concept implementation is here: #93 I tried the following to determine whether pinning makes any difference: use quanta::Upkeep;
use std::time::Duration;
fn main() {
// Takes about 7 seconds for sampling to run on my hardware.
const SAMPLES: usize = 1_000_000_000;
let _handle =
match Upkeep::new(Duration::from_millis(1)).start_pinned(core_affinity::CoreId { id: 0 }) {
Ok(handle) => handle,
Err(e) => panic!("{}", e),
};
/*
let _handle = match Upkeep::new(Duration::from_millis(1)).start() {
Ok(handle) => handle,
Err(e) => panic!("{}", e),
};
*/
let clock = quanta::Clock::new();
let mut then = clock.recent();
let mut samples = Vec::with_capacity(SAMPLES);
for _ in 0..SAMPLES {
let now = clock.recent();
let delta = now.duration_since(then);
then = now;
samples.push(delta);
}
let min = samples
.iter()
.filter(|sample| !sample.is_zero())
.min()
.unwrap()
.as_nanos();
let max = samples.iter().max().unwrap().as_nanos();
let samples = samples
.iter()
.map(Duration::as_nanos)
.filter(|&sample| sample != 0)
.map(|sample| sample as f64)
.collect::<Vec<_>>();
let samples = ndarray::Array::from_vec(samples);
println!("var: {}", samples.var(0.));
println!("std: {}", samples.std(0.));
println!("min: {}", min);
println!("max: {}", max);
println!("samples: {}", samples.len());
}
And these are the results when running a pinned
And when
I'm startled by that My hardware in question is an Intel i7-7700HQ. Inspecting Also, I don't mind if you don't accept the PR due to additional complexity/dependencies. I've just realized that this functionality can be implemented by using the |
Ok, just as I've written all of this, I've realized where diff --git a/src/main.rs b/src/main.rs
index f8aa6c2..6d4ee12 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -16,6 +16,7 @@ fn main() {
Ok(handle) => handle,
Err(e) => panic!("{}", e),
};
+ std::thread::sleep(Duration::from_secs(3));
let clock = quanta::Clock::new();
let mut then = clock.recent(); There's hardly any difference anymore between pinned:
and non-pinned version:
|
@gootorov Nice! Thanks for testing all of that out. I agree with your conclusion: for example, the standard deviation decreasing by ~1us with a 1ms interval configured is indeed a very small change in the overall dispersion. However, your testing revealed an interesting little ergonomic bug: we should always be taking an initial time measurement from the clock and setting the recent time with it before we start the upkeep thread, which as you've discovered, has some variable timing in terms of how long it takes to start. I opened #94 to track this. |
My knowledge regarding timestamp counters is limited. However, the discussions in #17 and #61 left me wondering - does it make sense to pin the
Upkeep
thread and prevent it from moving to other cores/sockets?Since
Upkeep
can move freely, and it callsClock::now()
periodically, is it possible to get a wildly different raw value after rescheduling, thus resulting in less than ideal measurement even after scaling? Would pinning improve accuracy (at least theoretically), or is it not a concern due to some other reasons?The text was updated successfully, but these errors were encountered: