Skip to content
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

Can Instants be NonZeroU64? #64

Open
hawkw opened this issue Jan 14, 2022 · 4 comments
Open

Can Instants be NonZeroU64? #64

hawkw opened this issue Jan 14, 2022 · 4 comments

Comments

@hawkw
Copy link
Contributor

hawkw commented Jan 14, 2022

Currently, quanta::Instant is represented as a single u64 value. Will Instants ever be 0? If we're reasonably confident that Instants with the value 0 will never be generated, we may want to consider changing the internal representation to NonZeroU64. This will permit Option<Instant> to be niche optimized into a single 64-bit value.

My particular use case for this is that I'd like to be able to store an Instant in an AtomicU64, and some of those instants may be initially unset. With the current quanta API, I can implement this myself using Instant::as_u64, and using 0 as the unset value in my code.

However, my understanding is that, in quanta 1.0, the intention is to make Instant opaque and remove the as_u64 method. This means that it will be necessary to switch to crossbeam_util's AtomicCell type to store Instants atomically. When using AtomicCell with opaque Instant types, there's no way to initialize those cells to an "empty" value. I could use a specific "program start time" Instant as the zero value, but it would have to be passed around to a lot of places, making this code significantly more awkward.

Instead, it would be really nice to be able to use AtomicCell<Option<Instant>> and have it be lock-free on platforms with 64-bit atomics. This would require that Option<Instant> occupy a single 64-bit word, which is only possible if Instant is represented as NonZeroU64.

@hawkw
Copy link
Contributor Author

hawkw commented Jan 14, 2022

Of course, if it's possible for quanta to ever generate Instants with the u64 value 0, this won't work, so we'd need to know for sure that this is the case. Otherwise, we'll need to find an alternative solution for my use case.

@tobz
Copy link
Member

tobz commented Jan 15, 2022

Thinking through this one a little bit, and putting my thoughts down in this issue...

I think, practically speaking, there is an infinitesimally small chance of generating an Instant whose raw value is 0. While our reference time anchor may initially be zero -- unlikely, very unlikely, but technically possible -- and our TSC scaling factor could end up scaling a raw TSC read to zero... it's just... super unlikely. That said, it's technically possible: timespec in libc uses signed integers for both the second and nanosecond components. We might end up somewhere before zero, with the TSC addition bringing it up to zero.

There's also the technically-possible-but-highly-unlikely issue of integer overflow/wraparound as we scale up the raw TSC value and then add it to the reference time anchor, bringing it inline with the output you'd get from clock_gettime(CLOCK_MONOTONIC) directly.

While we could always construct an Instant such that we bitwise-AND the value with one, to ensure it's non-zero, that's an extra instruction for every single time we build an Instant, which is annoying. Maybe not practically meaningful, performance-wise, certainly compared to the other heavy-handed stuff that std::time is doing... but still not great.

@tobz
Copy link
Member

tobz commented Jan 15, 2022

The other thing is that doing so would introduce a 1ns forward jump about half of the time, which isn't terrible in terms of accuracy, but it might make monotonicity invariants harder to achieve. Gotta think through that one...

@tobz
Copy link
Member

tobz commented Jan 17, 2022

Just to close the mental loop around the bitwise-AND idea for my own sake: I think this would work.

Since we're only ever adding to the value, we won't be changing the numbers such that they warp backwards in time, which upholds the monotonicity invariant between two values of Instant. At worst, we may end up with two measurements -- T0 and T1 -- that, when uncorrected, are 1 nanosecond apart. If T0 was even, we would increment it by 1ns, which would make it equal to T1.

I'll whip up a simple PR to benchmark the change.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants