Skip to content

runtime: nanosecond monotonic clock; fix Linux clock IDs#2015

Open
cpunion wants to merge 1 commit into
xgo-dev:mainfrom
cpunion:codex/time-monotonic-precision
Open

runtime: nanosecond monotonic clock; fix Linux clock IDs#2015
cpunion wants to merge 1 commit into
xgo-dev:mainfrom
cpunion:codex/time-monotonic-precision

Conversation

@cpunion

@cpunion cpunion commented Jul 2, 2026

Copy link
Copy Markdown
Collaborator

The monotonic time source had two independent problems:

  • On Linux, runtimeNano passed clite's CLOCK_MONOTONIC, whose value is Darwin's clock id (6). Linux interprets 6 as CLOCK_MONOTONIC_COARSE, a millisecond-granularity clock: consecutive time.Now() readings were identical 100% of the time and the smallest nonzero delta was 1ms. Timer precision, sync spin timing and any benchmark that trusts time.Since were all millisecond-quantized.
  • On Darwin, clock_gettime(CLOCK_MONOTONIC) itself only has microsecond granularity (96% identical consecutive readings, 1µs minimum delta).

Changes

Mirror Go's runtime structure with a per-OS nanotime1 in the runtime package itself, keeping the hot path free of clite indirection and clite unchanged:

  • Darwin reads CLOCK_UPTIME_RAW through clock_gettime_nsec_np — the same clock Go's nanotime uses there — via a direct C linkname.
  • Linux uses clock_gettime with the OS-correct CLOCK_MONOTONIC id as a local constant.
  • Remaining platforms keep the previous behavior.

Validation

Consecutive time.Now() delta probe (min nonzero delta / fraction of zero deltas), 200k iterations:

platform before after Go 1.26
macOS arm64 1µs / 96.5% 41ns / 26% 41ns / 22%
Linux arm64 (container) 1ms / 100% 41ns / 21%

time.Sleep / Timer / Ticker smoke: 50ms sleep → 51ms, 30ms timer → 31ms, 3×10ms ticker → 32ms, identical before and after. go test ./test/go -run 'Select|Channel' passes on macOS; builds and probes verified in the Linux arm64 container.

Found while benchmarking #2012: cold-path metrics on Linux read as 0ns because the monotonic clock could not resolve sub-millisecond durations.

🤖 Generated with Claude Code

@cpunion cpunion force-pushed the codex/time-monotonic-precision branch from 9d14354 to ea66de5 Compare July 2, 2026 08:56
@codecov

codecov Bot commented Jul 2, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.

📢 Thoughts on this report? Let us know!

The monotonic time source had two problems:

- On Linux, runtimeNano passed clite's CLOCK_MONOTONIC, whose value is
  Darwin's clock id (6). Linux interprets 6 as CLOCK_MONOTONIC_COARSE,
  a millisecond-granularity clock: consecutive time.Now() readings were
  identical 100% of the time and the smallest nonzero delta was 1ms.
- On Darwin, clock_gettime(CLOCK_MONOTONIC) itself only has microsecond
  granularity (96% identical consecutive readings, 1us minimum delta).

Mirror Go's runtime structure with a per-OS nanotime1 in the runtime
package itself, keeping the hot path free of clite indirection and clite
unchanged: Darwin reads CLOCK_UPTIME_RAW through clock_gettime_nsec_np
(the same clock Go's nanotime uses there), Linux uses clock_gettime with
the OS-correct CLOCK_MONOTONIC id as a local constant, and remaining
platforms keep the previous behavior.

Measured with consecutive time.Now() deltas (min nonzero / zero-frac):
- macOS arm64: 1us / 96.5%  ->  41ns / 26%  (Go 1.26: 41ns / 22%)
- Linux arm64: 1ms / 100%   ->  41ns / 21%

time.Sleep, Timer and Ticker behave identically before and after.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
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

Successfully merging this pull request may close these issues.

1 participant