Skip to content

Commit

Permalink
runtime: reduce Windows timer resolution when idle
Browse files Browse the repository at this point in the history
Currently Go sets the system-wide timer resolution to 1ms the whole
time it's running. This has negative affects on system performance and
power consumption. Unfortunately, simply reducing the timer resolution
to the default 15ms interferes with several sleeps in the runtime
itself, including sysmon's ability to interrupt goroutines.

This commit takes a hybrid approach: it only reduces the timer
resolution when the Go process is entirely idle. When the process is
idle, nothing needs a high resolution timer. When the process is
non-idle, it's already consuming CPU so it doesn't really matter if
the OS also takes timer interrupts more frequently.

Updates #8687.

Change-Id: I0652564b4a36d61a80e045040094a39c19da3b06
Reviewed-on: https://go-review.googlesource.com/38403
Run-TryBot: Austin Clements <austin@google.com>
TryBot-Result: Gobot Gobot <gobot@golang.org>
Reviewed-by: Alex Brainman <alex.brainman@gmail.com>
Reviewed-by: Dmitry Vyukov <dvyukov@google.com>
  • Loading branch information
aclements committed Apr 29, 2017
1 parent b225396 commit 11eaf42
Show file tree
Hide file tree
Showing 3 changed files with 31 additions and 1 deletion.
19 changes: 18 additions & 1 deletion src/runtime/os_windows.go
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@ const (
//go:cgo_import_dynamic runtime._WriteConsoleW WriteConsoleW%5 "kernel32.dll"
//go:cgo_import_dynamic runtime._WriteFile WriteFile%5 "kernel32.dll"
//go:cgo_import_dynamic runtime._timeBeginPeriod timeBeginPeriod%1 "winmm.dll"
//go:cgo_import_dynamic runtime._timeEndPeriod timeEndPeriod%1 "winmm.dll"

type stdFunction unsafe.Pointer

Expand Down Expand Up @@ -96,6 +97,7 @@ var (
_WriteConsoleW,
_WriteFile,
_timeBeginPeriod,
_timeEndPeriod,
_ stdFunction

// Following syscalls are only available on some Windows PCs.
Expand Down Expand Up @@ -268,6 +270,21 @@ var useLoadLibraryEx bool

var timeBeginPeriodRetValue uint32

// osRelax is called by the scheduler when transitioning to and from
// all Ps being idle.
//
// On Windows, it adjusts the system-wide timer resolution. Go needs a
// high resolution timer while running and there's little extra cost
// if we're already using the CPU, but if all Ps are idle there's no
// need to consume extra power to drive the high-res timer.
func osRelax(relax bool) uint32 {
if relax {
return uint32(stdcall1(_timeEndPeriod, 1))
} else {
return uint32(stdcall1(_timeBeginPeriod, 1))
}
}

func osinit() {
asmstdcallAddr = unsafe.Pointer(funcPC(asmstdcall))
usleep2Addr = unsafe.Pointer(funcPC(usleep2))
Expand All @@ -287,7 +304,7 @@ func osinit() {

stdcall2(_SetConsoleCtrlHandler, funcPC(ctrlhandler), 1)

timeBeginPeriodRetValue = uint32(stdcall1(_timeBeginPeriod, 1))
timeBeginPeriodRetValue = osRelax(false)

ncpu = getproccount()

Expand Down
2 changes: 2 additions & 0 deletions src/runtime/proc.go
Original file line number Diff line number Diff line change
Expand Up @@ -3751,7 +3751,9 @@ func sysmon() {
if scavengelimit < forcegcperiod {
maxsleep = scavengelimit / 2
}
osRelax(true)
notetsleep(&sched.sysmonnote, maxsleep)
osRelax(false)
lock(&sched.lock)
atomic.Store(&sched.sysmonwait, 0)
noteclear(&sched.sysmonnote)
Expand Down
11 changes: 11 additions & 0 deletions src/runtime/relax_stub.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
// Copyright 2017 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 !windows

package runtime

// osRelax is called by the scheduler when transitioning to and from
// all Ps being idle.
func osRelax(relax bool) {}

6 comments on commit 11eaf42

@stevenh
Copy link
Contributor

Choose a reason for hiding this comment

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

As this is a system wide option its not safe for go to make this decision as other apps my require the high frequency timer which is not not possible if the machine is running a golang binary.

@aclements
Copy link
Member Author

Choose a reason for hiding this comment

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

@stevenh, if another application sets the timer to high-resolution, Windows will use a high-resolution timer regardless of what Go applications have it set to. This simply relaxes this one process' timer requirement to the default.

@stevenh
Copy link
Contributor

@stevenh stevenh commented on 11eaf42 Oct 17, 2018

Choose a reason for hiding this comment

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

That doesn't appear to be the case even with NtSetTimerResolution called this seems to reset it back

@stevenh
Copy link
Contributor

@stevenh stevenh commented on 11eaf42 Oct 17, 2018

Choose a reason for hiding this comment

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

As a repoduction case I replaced return uint32(stdcall1(_timeEndPeriod, 1)) with return 0 and the machines running the golang daemons now stick at 0.5ms timers


Clockres v2.1 - Clock resolution display utility
Copyright (C) 2016 Mark Russinovich
Sysinternals

Maximum timer interval: 15.625 ms
Minimum timer interval: 0.500 ms
Current timer interval: 0.488 ms

@ianlancetaylor
Copy link
Member

Choose a reason for hiding this comment

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

Let's try not to have discussions on GitHub commits. Not many people see them and they will be lost. Please use an issue or communicate on Gerrit. Thanks.

@stevenh
Copy link
Contributor

Choose a reason for hiding this comment

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

yep already raised: #28255

Please sign in to comment.