Skip to content

Initial implementation of Stopwatch - #161817

Open
ChrisDenton wants to merge 2 commits into
rust-lang:mainfrom
ChrisDenton:stopwatch
Open

ChrisDenton wants to merge 2 commits into
rust-lang:mainfrom
ChrisDenton:stopwatch

Conversation

@ChrisDenton

@ChrisDenton ChrisDenton commented Aug 26, 2026 •

Copy link
Copy Markdown
Member

View all comments

For most platforms this currently delegates to Instant but they could drift apart later. For Linux I considered CLOCK_MONOTONIC_RAW but I wasn't fully convinced it was the right thing to do since adjustments do make CLOCK_MONOTONIC more accurate (and apparently there's a bug in older kernels that make calls to CLOCK_MONOTONIC_RAW slower). Windows currently uses QueryPerformanceCounter for both Instant and Stopwatch but without the need to convert to a duration as soon as getting the counter. It is expected for Instant to change in the future to use the same clock as timeouts, etc.

Tracking issue: #161809

r? joboet

@rustbot rustbot added the S-waiting-on-review Status: Awaiting review from the assignee but also interested parties. label Aug 26, 2026
@rustbot rustbot added the T-libs Relevant to the library team, which will review and decide on the PR/issue. label Aug 26, 2026
@ChrisDenton
ChrisDenton force-pushed the stopwatch branch 2 times, most recently from f1788d9 to 8c3406c Compare August 26, 2026 14:54
@ChrisDenton

ChrisDenton commented Aug 26, 2026 •

Copy link
Copy Markdown
Member Author

Huh, I'm not actually sure about apple. I thought CLOCK_MONOTONIC would resolve to the right thing but it seems like it's not actually the monotonic clock? I'll use mach_absolute_time directly.

Comment thread library/std/src/sys/time/unix.rs Outdated
@ChrisDenton
ChrisDenton force-pushed the stopwatch branch 2 times, most recently from 7983c80 to 2c64ef2 Compare August 26, 2026 16:17
@ChrisDenton

Copy link
Copy Markdown
Member Author

@bors try jobs=aarch64-apple-*

@rust-bors

This comment has been minimized.

rust-bors Bot pushed a commit that referenced this pull request Aug 26, 2026
Initial implementation of `Stopwatch`


try-job: aarch64-apple-*
@rust-log-analyzer

This comment has been minimized.

@rust-bors

rust-bors Bot commented Aug 26, 2026

Copy link
Copy Markdown
Contributor

☀️ Try build successful (CI)
Build commit: 5207e14 (5207e14db106fcd737e2cd60d4dacee9799db329)
Base parent: c241221 (c24122146bb8b9cfd9e5c067e0f736c59f5826a1)

@joboet

joboet commented Aug 26, 2026

Copy link
Copy Markdown
Member

Huh, I'm not actually sure about apple. I thought CLOCK_MONOTONIC would resolve to the right thing but it seems like it's not actually the monotonic clock? I'll use mach_absolute_time directly.

It is monotonic, but it's measured by subtracting the system boot time from gettimeofday and thus only offers microsecond-accuracy. We currently go through CLOCK_UPTIME_RAW instead, which is equivalent to mach_absolute_time, but does all the nanosecond conversion internally. So there's definitely something to be gained by using mach_absolute_time for Stopwatch. There's also mach_continuous_time which measures time during suspend (and backs CLOCK_MONOTONIC_RAW), but I'm not sure whether that's important here...

Comment thread library/std/src/sys/time/windows.rs Outdated
Comment thread library/std/src/sys/time/mod.rs
Comment thread library/std/src/sys/time/unix/apple.rs Outdated
Comment thread library/std/src/time.rs Outdated
/// | SGX | [`insecure_time` usercall]. More information on [timekeeping in SGX] |
/// | UNIX | [clock_gettime] with `CLOCK_MONOTONIC` |
/// | WASI | [clock_gettime] with `CLOCK_MONOTONIC` |
/// | Darwin | [clock_gettime] with `CLOCK_MONOTONIC` |

@joboet joboet Aug 27, 2026 •

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This will need updating...

View changes since the review

Comment thread library/std/src/time.rs Outdated
/// The following system calls are [currently] being used by `now()` to find out
/// the current time:
///
/// | Platform | System call |

@joboet joboet Aug 27, 2026 •

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I wonder whether it'd be easier to only mention the platforms where Stopwatch deviates from Instant.

View changes since the review

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think if/when this is stable it'd be good to consulate some of these docs at the module level. But in the meantime I guess it makes sense to keep duplication to a minimum.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh yeah, module-level sounds great! I've just noticed a tendency for these tables to fall out of sync with the actual implementation, so keeping duplication to a minimum hopefully helps prevent that.

@rustbot rustbot added S-waiting-on-author Status: This is awaiting some action (such as code changes or more information) from the author. and removed S-waiting-on-review Status: Awaiting review from the assignee but also interested parties. labels Aug 27, 2026
@rustbot rustbot added O-unix Operating system: Unix-like O-windows Operating system: Windows labels Aug 31, 2026

@ChrisDenton ChrisDenton Aug 31, 2026 •

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I got a bit side tracked by trying to optimise the windows performance counter. I realised if you give the compiler enough information it will optimise down to a single imul instruction on x86/x64. It's less dramatic on arm64 but still an improvement. See godbolt. The values of 10_000_000 and 24_000_000 for frequency apply to x86/x64 and aarch64 respectively. This will cover most real-world cases.

Using niche types might be a bit OTT but having the information be carried by the types does seem more natural to me than assert_unchecked.

View changes since the review

Comment thread library/std/src/sys/pal/windows/time.rs Outdated
let mut qpc_value: i64 = 0;
// `QueryPerformanceCounter` will never fail (since XP).
unsafe { c::QueryPerformanceCounter(&mut qpc_value) };
// SAFETY: QueryPerformanceCounter returns a positive integer.

@joboet joboet Aug 31, 2026 •

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could you add a reference for that? E.g. https://learn.microsoft.com/en-us/windows/win32/sysinfo/acquiring-high-resolution-time-stamps saying that "QueryPerformanceCounter reads the performance counter and returns the total number of ticks that have occurred since the Windows operating system was started".

View changes since the review

Comment thread library/std/src/sys/pal/windows/time.rs Outdated
pub fn checked_sub(self, other: Self) -> Option<Self> {
self.as_u64()
.checked_sub(other.as_u64())
// SAFETY: u64::checked_sub ensures the result will be >=0 and <=i64::MAX.

@joboet joboet Aug 31, 2026 •

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's not u64::checked_sub that ensures <= i64::MAX, but the fact that both self and other are positive, so I found this a bit misleading.

View changes since the review

@ChrisDenton ChrisDenton Aug 31, 2026 •

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sure. I was thinking of it in terms of the as cast's affect on converting an u64 to a i64 (so checked_sub prevents the negative case) but I can see that's confusing.

Comment on lines +35 to +36
} else if freq == 24_000_000 && cfg!(target_arch = "aarch64") {
mul_div_u64(counter, magnitude, freq)

@ChrisDenton ChrisDenton Aug 31, 2026 •

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ok, so this is funky on aarch64. I need the freq == 10_100_000 branch (even if it's never taken) otherwise the codegen actually gets worse: https://rust.godbolt.org/z/cd4eE1edT

View changes since the review

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Huh, weird...

}

pub fn checked_duration_since(self, other: Stopwatch) -> Option<Duration> {
let diff = self.ticks.checked_sub(other.ticks)?;

@joboet joboet Aug 31, 2026 •

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This might make this function return None in the cross-thread case, whereas Instant would return Duration::ZERO.

View changes since the review


pub fn checked_duration_since(self, other: Stopwatch) -> Option<Duration> {
let diff = self.ticks.checked_sub(other.ticks)?;
if diff.as_u64() <= 1 {

@joboet joboet Aug 31, 2026 •

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it's better to return the proper difference in this case, there is little harm in returning it here. The case where other is self + 1 is the one where I'd return Duration::ZERO. But anyhow, I'm not sure if the note in the documentation still applies, I can well imagine it to be outdated at this point – especially given that none of the other operating systems seem to have problems with cross-thread clock synchronisation.

View changes since the review

@ChrisDenton ChrisDenton Aug 31, 2026 •

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hm, I'll want to investigate some more but I am minded to just remove the +/- 1 case.

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

O-unix Operating system: Unix-like O-windows Operating system: Windows S-waiting-on-author Status: This is awaiting some action (such as code changes or more information) from the author. T-libs Relevant to the library team, which will review and decide on the PR/issue.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants