mirror of
https://github.com/tailscale/tailscale.git
synced 2026-04-04 22:53:38 -04:00
tstime: add MonotonicCoarse
MonotonicCoarse provides a coarse monotonic time. On some platforms, it is implemented in assembly, which lets us do much less work than time.Now, which gets a high precision monotonic time and a high precision wall time. The assembly code is tied to a particular Go release because it reaches into the Go internals in order to switch to the system stack for the vdso call. On my darwin/arm64 machine, there is no perf difference. On my linux/amd64 machine, MonotonicCoarse is 5x faster (50ns -> 10ns). On my linux/arm64 VM, MonotonicCoarse is 16x faster (64ns -> 4ns). We could also use this in the rate limiter and magicsock, which are two other uses of time.Now that show up in the CPU pprof when doing throughput benchmarking. Signed-off-by: Josh Bleecher Snyder <josh@tailscale.com>
This commit is contained in:
@@ -18,6 +18,7 @@
|
||||
"golang.zx2c4.com/wireguard/tun"
|
||||
"inet.af/netaddr"
|
||||
"tailscale.com/net/packet"
|
||||
"tailscale.com/tstime"
|
||||
"tailscale.com/types/ipproto"
|
||||
"tailscale.com/types/logger"
|
||||
"tailscale.com/wgengine/filter"
|
||||
@@ -363,7 +364,7 @@ func (t *Wrapper) filterOut(p *packet.Parsed) filter.Response {
|
||||
|
||||
// noteActivity records that there was a read or write at the current time.
|
||||
func (t *Wrapper) noteActivity() {
|
||||
atomic.StoreInt64(&t.lastActivityAtomic, time.Now().Unix())
|
||||
atomic.StoreInt64(&t.lastActivityAtomic, tstime.MonotonicCoarse())
|
||||
}
|
||||
|
||||
// IdleDuration reports how long it's been since the last read or write to this device.
|
||||
@@ -371,8 +372,8 @@ func (t *Wrapper) noteActivity() {
|
||||
// Its value is only accurate to roughly second granularity.
|
||||
// If there's never been activity, the duration is since 1970.
|
||||
func (t *Wrapper) IdleDuration() time.Duration {
|
||||
sec := atomic.LoadInt64(&t.lastActivityAtomic)
|
||||
return time.Since(time.Unix(sec, 0))
|
||||
elapsed := tstime.MonotonicCoarse() - atomic.LoadInt64(&t.lastActivityAtomic)
|
||||
return time.Duration(elapsed) * time.Second
|
||||
}
|
||||
|
||||
func (t *Wrapper) Read(buf []byte, offset int) (int, error) {
|
||||
|
||||
11
tstime/monocoarse_asm.go
Normal file
11
tstime/monocoarse_asm.go
Normal file
@@ -0,0 +1,11 @@
|
||||
// +build go1.16,!go1.17
|
||||
// +build linux,amd64 linux,arm64
|
||||
|
||||
package tstime
|
||||
|
||||
// MonotonicCoarse returns the number of monotonic seconds elapsed
|
||||
// since an unspecified starting point, at low precision.
|
||||
// It is only meaningful when compared to the
|
||||
// result of previous MonotonicCoarse calls.
|
||||
// On some platforms, MonotonicCoarse is much faster than time.Now.
|
||||
func MonotonicCoarse() int64
|
||||
105
tstime/monocoarse_linux_amd64.s
Normal file
105
tstime/monocoarse_linux_amd64.s
Normal file
@@ -0,0 +1,105 @@
|
||||
// Copyright (c) 2021 Tailscale Inc & AUTHORS All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
// Adapted from code in the Go runtime package at Go 1.16.6:
|
||||
|
||||
// Copyright 2009 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
// +build go1.16
|
||||
// +build !go1.17
|
||||
|
||||
#include "go_asm.h"
|
||||
#include "textflag.h"
|
||||
|
||||
#define get_tls(r) MOVQ TLS, r
|
||||
#define g(r) 0(r)(TLS*1)
|
||||
|
||||
#define SYS_clock_gettime 228
|
||||
|
||||
// Hard-coded offsets into runtime structs.
|
||||
// Generated by adding the following code
|
||||
// to package runtime and then executing
|
||||
// and empty func main:
|
||||
//
|
||||
// func init() {
|
||||
// println("#define g_m", unsafe.Offsetof(g{}.m))
|
||||
// println("#define g_sched", unsafe.Offsetof(g{}.sched))
|
||||
// println("#define gobuf_sp", unsafe.Offsetof(g{}.sched.sp))
|
||||
// println("#define m_g0", unsafe.Offsetof(m{}.g0))
|
||||
// println("#define m_curg", unsafe.Offsetof(m{}.curg))
|
||||
// println("#define m_vdsoSP", unsafe.Offsetof(m{}.vdsoSP))
|
||||
// println("#define m_vdsoPC", unsafe.Offsetof(m{}.vdsoPC))
|
||||
// }
|
||||
|
||||
#define g_m 48
|
||||
#define g_sched 56
|
||||
#define gobuf_sp 0
|
||||
#define m_g0 0
|
||||
#define m_curg 192
|
||||
#define m_vdsoSP 832
|
||||
#define m_vdsoPC 840
|
||||
|
||||
#define CLOCK_MONOTONIC_COARSE 6
|
||||
|
||||
// func MonotonicCoarse() int64
|
||||
TEXT ·MonotonicCoarse(SB),NOSPLIT,$16-8
|
||||
// Switch to g0 stack.
|
||||
|
||||
MOVQ SP, R12 // Save old SP; R12 unchanged by C code.
|
||||
|
||||
get_tls(CX)
|
||||
MOVQ g(CX), AX
|
||||
MOVQ g_m(AX), BX // BX unchanged by C code.
|
||||
|
||||
// Set vdsoPC and vdsoSP for SIGPROF traceback.
|
||||
// Save the old values on stack and restore them on exit,
|
||||
// so this function is reentrant.
|
||||
MOVQ m_vdsoPC(BX), CX
|
||||
MOVQ m_vdsoSP(BX), DX
|
||||
MOVQ CX, 0(SP)
|
||||
MOVQ DX, 8(SP)
|
||||
|
||||
LEAQ ret+0(FP), DX
|
||||
MOVQ -8(DX), CX
|
||||
MOVQ CX, m_vdsoPC(BX)
|
||||
MOVQ DX, m_vdsoSP(BX)
|
||||
|
||||
CMPQ AX, m_curg(BX) // Only switch if on curg.
|
||||
JNE noswitch
|
||||
|
||||
MOVQ m_g0(BX), DX
|
||||
MOVQ (g_sched+gobuf_sp)(DX), SP // Set SP to g0 stack
|
||||
|
||||
noswitch:
|
||||
SUBQ $16, SP // Space for results
|
||||
ANDQ $~15, SP // Align for C code
|
||||
|
||||
MOVL $CLOCK_MONOTONIC_COARSE, DI
|
||||
LEAQ 0(SP), SI
|
||||
MOVQ runtime·vdsoClockgettimeSym(SB), AX
|
||||
CMPQ AX, $0
|
||||
JEQ fallback
|
||||
CALL AX
|
||||
ret:
|
||||
MOVQ 0(SP), AX // sec
|
||||
MOVQ 8(SP), DX // nsec
|
||||
MOVQ R12, SP // Restore real SP
|
||||
// Restore vdsoPC, vdsoSP
|
||||
// We don't worry about being signaled between the two stores.
|
||||
// If we are not in a signal handler, we'll restore vdsoSP to 0,
|
||||
// and no one will care about vdsoPC. If we are in a signal handler,
|
||||
// we cannot receive another signal.
|
||||
MOVQ 8(SP), CX
|
||||
MOVQ CX, m_vdsoSP(BX)
|
||||
MOVQ 0(SP), CX
|
||||
MOVQ CX, m_vdsoPC(BX)
|
||||
// sec is in AX; return it
|
||||
MOVQ AX, ret+0(FP)
|
||||
RET
|
||||
fallback:
|
||||
MOVQ $SYS_clock_gettime, AX
|
||||
SYSCALL
|
||||
JMP ret
|
||||
132
tstime/monocoarse_linux_arm64.s
Normal file
132
tstime/monocoarse_linux_arm64.s
Normal file
@@ -0,0 +1,132 @@
|
||||
// Copyright (c) 2021 Tailscale Inc & AUTHORS All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
// Adapted from code in the Go runtime package at Go 1.16.6:
|
||||
|
||||
// Copyright 2009 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
// +build go1.16
|
||||
// +build !go1.17
|
||||
|
||||
#include "go_asm.h"
|
||||
#include "textflag.h"
|
||||
|
||||
#define SYS_clock_gettime 113
|
||||
|
||||
// Hard-coded offsets into runtime structs.
|
||||
// Generated by adding the following code
|
||||
// to package runtime and then executing
|
||||
// and empty func main:
|
||||
//
|
||||
// func init() {
|
||||
// println("#define g_m", unsafe.Offsetof(g{}.m))
|
||||
// println("#define g_sched", unsafe.Offsetof(g{}.sched))
|
||||
// println("#define gobuf_sp", unsafe.Offsetof(g{}.sched.sp))
|
||||
// println("#define g_stack", unsafe.Offsetof(g{}.stack))
|
||||
// println("#define stack_lo", unsafe.Offsetof(g{}.stack.lo))
|
||||
// println("#define m_g0", unsafe.Offsetof(m{}.g0))
|
||||
// println("#define m_curg", unsafe.Offsetof(m{}.curg))
|
||||
// println("#define m_vdsoSP", unsafe.Offsetof(m{}.vdsoSP))
|
||||
// println("#define m_vdsoPC", unsafe.Offsetof(m{}.vdsoPC))
|
||||
// println("#define m_gsignal", unsafe.Offsetof(m{}.gsignal))
|
||||
// }
|
||||
|
||||
#define g_m 48
|
||||
#define g_sched 56
|
||||
#define gobuf_sp 0
|
||||
#define g_stack 0
|
||||
#define stack_lo 0
|
||||
#define m_g0 0
|
||||
#define m_curg 192
|
||||
#define m_vdsoSP 832
|
||||
#define m_vdsoPC 840
|
||||
#define m_gsignal 80
|
||||
|
||||
#define CLOCK_MONOTONIC_COARSE 6
|
||||
|
||||
// func MonotonicCoarse() int64
|
||||
TEXT ·MonotonicCoarse(SB),NOSPLIT,$24-8
|
||||
MOVD RSP, R20 // R20 is unchanged by C code
|
||||
MOVD RSP, R1
|
||||
|
||||
MOVD g_m(g), R21 // R21 = m
|
||||
|
||||
// Set vdsoPC and vdsoSP for SIGPROF traceback.
|
||||
// Save the old values on stack and restore them on exit,
|
||||
// so this function is reentrant.
|
||||
MOVD m_vdsoPC(R21), R2
|
||||
MOVD m_vdsoSP(R21), R3
|
||||
MOVD R2, 8(RSP)
|
||||
MOVD R3, 16(RSP)
|
||||
|
||||
MOVD LR, m_vdsoPC(R21)
|
||||
MOVD R20, m_vdsoSP(R21)
|
||||
|
||||
MOVD m_curg(R21), R0
|
||||
CMP g, R0
|
||||
BNE noswitch
|
||||
|
||||
MOVD m_g0(R21), R3
|
||||
MOVD (g_sched+gobuf_sp)(R3), R1 // Set RSP to g0 stack
|
||||
|
||||
noswitch:
|
||||
SUB $32, R1
|
||||
BIC $15, R1
|
||||
MOVD R1, RSP
|
||||
|
||||
MOVW $CLOCK_MONOTONIC_COARSE, R0
|
||||
MOVD runtime·vdsoClockgettimeSym(SB), R2
|
||||
CBZ R2, fallback
|
||||
|
||||
// Store g on gsignal's stack, so if we receive a signal
|
||||
// during VDSO code we can find the g.
|
||||
// If we don't have a signal stack, we won't receive signal,
|
||||
// so don't bother saving g.
|
||||
// When using cgo, we already saved g on TLS, also don't save
|
||||
// g here.
|
||||
// Also don't save g if we are already on the signal stack.
|
||||
// We won't get a nested signal.
|
||||
MOVBU runtime·iscgo(SB), R22
|
||||
CBNZ R22, nosaveg
|
||||
MOVD m_gsignal(R21), R22 // g.m.gsignal
|
||||
CBZ R22, nosaveg
|
||||
CMP g, R22
|
||||
BEQ nosaveg
|
||||
MOVD (g_stack+stack_lo)(R22), R22 // g.m.gsignal.stack.lo
|
||||
MOVD g, (R22)
|
||||
|
||||
BL (R2)
|
||||
|
||||
MOVD ZR, (R22) // clear g slot, R22 is unchanged by C code
|
||||
|
||||
B finish
|
||||
|
||||
nosaveg:
|
||||
BL (R2)
|
||||
B finish
|
||||
|
||||
fallback:
|
||||
MOVD $SYS_clock_gettime, R8
|
||||
SVC
|
||||
|
||||
finish:
|
||||
MOVD 0(RSP), R3 // sec
|
||||
MOVD 8(RSP), R5 // nsec
|
||||
|
||||
MOVD R20, RSP // restore SP
|
||||
// Restore vdsoPC, vdsoSP
|
||||
// We don't worry about being signaled between the two stores.
|
||||
// If we are not in a signal handler, we'll restore vdsoSP to 0,
|
||||
// and no one will care about vdsoPC. If we are in a signal handler,
|
||||
// we cannot receive another signal.
|
||||
MOVD 16(RSP), R1
|
||||
MOVD R1, m_vdsoSP(R21)
|
||||
MOVD 8(RSP), R1
|
||||
MOVD R1, m_vdsoPC(R21)
|
||||
|
||||
// sec is in R3; return it
|
||||
MOVD R3, ret+0(FP)
|
||||
RET
|
||||
16
tstime/monocoarse_std.go
Normal file
16
tstime/monocoarse_std.go
Normal file
@@ -0,0 +1,16 @@
|
||||
// +build !go1.16 go1.17 !linux !amd64,!arm64
|
||||
|
||||
package tstime
|
||||
|
||||
import "time"
|
||||
|
||||
var referenceTime = time.Now()
|
||||
|
||||
// MonotonicCoarse returns the number of monotonic seconds elapsed
|
||||
// since an unspecified starting point, at low precision.
|
||||
// It is only meaningful when compared to the
|
||||
// result of previous MonotonicCoarse calls.
|
||||
// On some platforms, MonotonicCoarse is much faster than time.Now.
|
||||
func MonotonicCoarse() int64 {
|
||||
return int64(time.Since(referenceTime).Seconds())
|
||||
}
|
||||
29
tstime/monocoarse_test.go
Normal file
29
tstime/monocoarse_test.go
Normal file
@@ -0,0 +1,29 @@
|
||||
package tstime
|
||||
|
||||
import (
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
func TestMonotonicCoarse(t *testing.T) {
|
||||
t.Parallel()
|
||||
start := MonotonicCoarse()
|
||||
for n := 0; n < 30; n++ {
|
||||
end := MonotonicCoarse()
|
||||
if end == start {
|
||||
time.Sleep(100 * time.Millisecond)
|
||||
continue
|
||||
}
|
||||
if end-start != 1 {
|
||||
t.Errorf("monotonic coarse time jumped: %v seconds", end-start)
|
||||
}
|
||||
return // success
|
||||
}
|
||||
t.Errorf("monotonic coarse time did not progress after 3s")
|
||||
}
|
||||
|
||||
func BenchmarkMonotonicCoarse(b *testing.B) {
|
||||
for i := 0; i < b.N; i++ {
|
||||
MonotonicCoarse()
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user