aboutsummaryrefslogtreecommitdiffstatshomepage
path: root/go-patches
diff options
context:
space:
mode:
authorJason A. Donenfeld <Jason@zx2c4.com>2020-08-18 09:47:34 +0200
committerJason A. Donenfeld <Jason@zx2c4.com>2020-08-18 13:34:48 +0200
commit890b42e68fed156e7d5e8b7306e77356ec1dcb2c (patch)
tree7e75a1fd1ded90f5d9293f49bb9944803f1bb95a /go-patches
parentgo: remove exit(2) from ctrl+c handler (diff)
downloadwireguard-windows-890b42e68fed156e7d5e8b7306e77356ec1dcb2c.tar.xz
wireguard-windows-890b42e68fed156e7d5e8b7306e77356ec1dcb2c.zip
go: use highres timer on newer Windows 10
WireGuard makes heavy use of timers, and the low precision of Windows timers as currently used in Go isn't very nice, and also seems to result in worse battery life because of the gross winmm.dll timerBeginPeriod trick. John Starks suggested this new parameter to make a high resolution timer on NT, and Alex implemented it, so import Alex's test patch to see how this goes. Signed-off-by: Jason A. Donenfeld <Jason@zx2c4.com>
Diffstat (limited to 'go-patches')
-rw-r--r--go-patches/highres-timer.patch395
1 files changed, 395 insertions, 0 deletions
diff --git a/go-patches/highres-timer.patch b/go-patches/highres-timer.patch
new file mode 100644
index 00000000..cdf47939
--- /dev/null
+++ b/go-patches/highres-timer.patch
@@ -0,0 +1,395 @@
+From 0b802790a76f4781c83e8fd400857bcd365b6a23 Mon Sep 17 00:00:00 2001
+From: Alex Brainman <alex.brainman@gmail.com>
+Date: Sun, 19 Jul 2020 16:06:48 +1000
+Subject: [PATCH] runtime: use CreateWaitableTimerEx to implement usleep
+
+@jstarks suggested that recent versions of Windows provide access to high resolution timers. See
+
+https://github.com/golang/go/issues/8687#issuecomment-656259353
+
+for details.
+
+I tried to run this C program on my Windows 10 computer
+
+```
+ #include <stdio.h>
+ #include <Windows.h>
+
+ #pragma comment(lib, "Winmm.lib")
+
+// Apparently this is already defined when I use msvc cl.
+//#define CREATE_WAITABLE_TIMER_HIGH_RESOLUTION = 0x00000002;
+
+int usleep(HANDLE timer, LONGLONG d) {
+ LARGE_INTEGER liDueTime;
+ DWORD ret;
+ LARGE_INTEGER StartingTime, EndingTime, ElapsedMicroseconds;
+ LARGE_INTEGER Frequency;
+
+ QueryPerformanceFrequency(&Frequency);
+ QueryPerformanceCounter(&StartingTime);
+
+ liDueTime.QuadPart = d;
+ liDueTime.QuadPart = liDueTime.QuadPart * 10; // us into 100 of ns units
+ liDueTime.QuadPart = -liDueTime.QuadPart; // negative for relative dure time
+
+ if (!SetWaitableTimer(timer, &liDueTime, 0, NULL, NULL, 0)) {
+ printf("SetWaitableTimer failed: errno=%d\n", GetLastError());
+ return 1;
+ }
+
+ ret = WaitForSingleObject(timer, INFINITE);
+ if (ret != WAIT_OBJECT_0) {
+ printf("WaitForSingleObject failed: ret=%d errno=%d\n", ret, GetLastError());
+ return 1;
+ }
+
+ QueryPerformanceCounter(&EndingTime);
+ ElapsedMicroseconds.QuadPart = EndingTime.QuadPart - StartingTime.QuadPart;
+ ElapsedMicroseconds.QuadPart *= 1000000;
+ ElapsedMicroseconds.QuadPart /= Frequency.QuadPart;
+
+ printf("delay is %lld us - slept for %lld us\n", d, ElapsedMicroseconds.QuadPart);
+
+ return 0;
+}
+
+int testTimer(DWORD createFlag)
+{
+ HANDLE timer;
+
+ timer = CreateWaitableTimerEx(NULL, NULL, createFlag, TIMER_ALL_ACCESS);
+ if (timer == NULL) {
+ printf("CreateWaitableTimerEx failed: errno=%d\n", GetLastError());
+ return 1;
+ }
+
+ usleep(timer, 1000LL);
+ usleep(timer, 100LL);
+ usleep(timer, 10LL);
+ usleep(timer, 1LL);
+
+ CloseHandle(timer);
+
+ return 0;
+}
+
+int main()
+{
+ printf("\n1. CREATE_WAITABLE_TIMER_HIGH_RESOLUTION is off - timeBeginPeriod is off\n");
+ testTimer(0);
+
+ printf("\n2. CREATE_WAITABLE_TIMER_HIGH_RESOLUTION is on - timeBeginPeriod is off\n");
+ testTimer(CREATE_WAITABLE_TIMER_HIGH_RESOLUTION);
+
+ timeBeginPeriod(1);
+
+ printf("\n3. CREATE_WAITABLE_TIMER_HIGH_RESOLUTION is off - timeBeginPeriod is on\n");
+ testTimer(0);
+
+ printf("\n4. CREATE_WAITABLE_TIMER_HIGH_RESOLUTION is on - timeBeginPeriod is on\n");
+ testTimer(CREATE_WAITABLE_TIMER_HIGH_RESOLUTION);
+}
+```
+
+and I see this output
+
+```
+1. CREATE_WAITABLE_TIMER_HIGH_RESOLUTION is off - timeBeginPeriod is off
+delay is 1000 us - slept for 4045 us
+delay is 100 us - slept for 3915 us
+delay is 10 us - slept for 3291 us
+delay is 1 us - slept for 2234 us
+
+2. CREATE_WAITABLE_TIMER_HIGH_RESOLUTION is on - timeBeginPeriod is off
+delay is 1000 us - slept for 1076 us
+delay is 100 us - slept for 569 us
+delay is 10 us - slept for 585 us
+delay is 1 us - slept for 17 us
+
+3. CREATE_WAITABLE_TIMER_HIGH_RESOLUTION is off - timeBeginPeriod is on
+delay is 1000 us - slept for 742 us
+delay is 100 us - slept for 893 us
+delay is 10 us - slept for 414 us
+delay is 1 us - slept for 920 us
+
+4. CREATE_WAITABLE_TIMER_HIGH_RESOLUTION is on - timeBeginPeriod is on
+delay is 1000 us - slept for 1466 us
+delay is 100 us - slept for 559 us
+delay is 10 us - slept for 535 us
+delay is 1 us - slept for 5 us
+```
+
+That shows, that indeed using CREATE_WAITABLE_TIMER_HIGH_RESOLUTION
+will provide sleeps as low as about 500 microseconds, while our
+current approach provides about 1 millisecond sleep.
+
+New approach also does not require for timeBeginPeriod to be on,
+so this change solves long standing problem with go programs draining
+laptop battery, because it calls timeBeginPeriod.
+
+This change will only run on systems where
+CREATE_WAITABLE_TIMER_HIGH_RESOLUTION flag is available. If not
+available, the runtime will fallback to original code that uses
+timeBeginPeriod.
+
+This is how this change affects benchmark reported in issue #14790
+
+name               old time/op  new time/op  delta
+ChanToSyscallPing  1.05ms ± 2%  0.68ms ±11%  -35.43%  (p=0.000 n=10+10)
+
+The benchmark was run with GOMAXPROCS set to 1.
+
+Fixes #8687
+Updates #14790
+
+Change-Id: I5b97ba58289c088c17c05292e12e45285c467eae
+---
+
+diff --git a/src/runtime/os_windows.go b/src/runtime/os_windows.go
+index a584ada..5ca492b 100644
+--- a/src/runtime/os_windows.go
++++ b/src/runtime/os_windows.go
+@@ -21,6 +21,7 @@
+ //go:cgo_import_dynamic runtime._CreateIoCompletionPort CreateIoCompletionPort%4 "kernel32.dll"
+ //go:cgo_import_dynamic runtime._CreateThread CreateThread%6 "kernel32.dll"
+ //go:cgo_import_dynamic runtime._CreateWaitableTimerA CreateWaitableTimerA%3 "kernel32.dll"
++//go:cgo_import_dynamic runtime._CreateWaitableTimerExW CreateWaitableTimerExW%4 "kernel32.dll"
+ //go:cgo_import_dynamic runtime._DuplicateHandle DuplicateHandle%7 "kernel32.dll"
+ //go:cgo_import_dynamic runtime._ExitProcess ExitProcess%1 "kernel32.dll"
+ //go:cgo_import_dynamic runtime._FreeEnvironmentStringsW FreeEnvironmentStringsW%1 "kernel32.dll"
+@@ -68,6 +69,7 @@
+ _CreateIoCompletionPort,
+ _CreateThread,
+ _CreateWaitableTimerA,
++ _CreateWaitableTimerExW,
+ _DuplicateHandle,
+ _ExitProcess,
+ _FreeEnvironmentStringsW,
+@@ -151,6 +153,8 @@
+ waitsema uintptr // semaphore for parking on locks
+ resumesema uintptr // semaphore to indicate suspend/resume
+
++ highResTimer uintptr // high resolution timer handle used in usleep
++
+ // preemptExtLock synchronizes preemptM with entry/exit from
+ // external C code.
+ //
+@@ -407,6 +411,12 @@
+ // 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 haveHighResTimer {
++ // Only call timeBeginPeriod/timeEndPeriod, if
++ // high resolution timer is not available.
++ return 0
++ }
++
+ if relax {
+ return uint32(stdcall1(_timeEndPeriod, 1))
+ } else {
+@@ -414,6 +424,37 @@
+ }
+ }
+
++// haveHighResTimer determines, if CreateWaitableTimerEx
++// CREATE_WAITABLE_TIMER_HIGH_RESOLUTION flag is available.
++var haveHighResTimer = false
++
++// createHighResTimer calls CreateWaitableTimerEx with
++// CREATE_WAITABLE_TIMER_HIGH_RESOLUTION flag to create high
++// resolution timer. createHighResTimer returns new timer
++// handle or 0, if CreateWaitableTimerEx failed.
++func createHighResTimer() uintptr {
++ const (
++ // As per @jstarks, see
++ // https://github.com/golang/go/issues/8687#issuecomment-656259353
++ _CREATE_WAITABLE_TIMER_HIGH_RESOLUTION = 0x00000002
++ _TIMER_ALL_ACCESS = 0x1F0003
++ )
++ return stdcall4(_CreateWaitableTimerExW, 0, 0, _CREATE_WAITABLE_TIMER_HIGH_RESOLUTION, _TIMER_ALL_ACCESS)
++}
++
++func initHighResTimer() {
++ if GOARCH == "arm" {
++ // TODO: Not implemented, if we running on arm.
++ return
++ }
++ h := createHighResTimer()
++ if h != 0 {
++ haveHighResTimer = true
++ usleep2Addr = unsafe.Pointer(funcPC(usleep2HighRes))
++ stdcall1(_CloseHandle, h)
++ }
++}
++
+ func osinit() {
+ asmstdcallAddr = unsafe.Pointer(funcPC(asmstdcall))
+ usleep2Addr = unsafe.Pointer(funcPC(usleep2))
+@@ -429,6 +470,7 @@
+
+ stdcall2(_SetConsoleCtrlHandler, funcPC(ctrlhandler), 1)
+
++ initHighResTimer()
+ timeBeginPeriodRetValue = osRelax(false)
+
+ ncpu = getproccount()
+@@ -844,9 +886,20 @@
+ var thandle uintptr
+ stdcall7(_DuplicateHandle, currentProcess, currentThread, currentProcess, uintptr(unsafe.Pointer(&thandle)), 0, 0, _DUPLICATE_SAME_ACCESS)
+
++ // Configure usleep timer, if possible.
++ var timer uintptr
++ if haveHighResTimer {
++ timer = createHighResTimer()
++ if timer == 0 {
++ print("runtime: CreateWaitableTimerEx failed; errno=", getlasterror(), "\n")
++ throw("CreateWaitableTimerEx when creating timer failed")
++ }
++ }
++
+ mp := getg().m
+ lock(&mp.threadLock)
+ mp.thread = thandle
++ mp.highResTimer = timer
+ unlock(&mp.threadLock)
+
+ // Query the true stack base from the OS. Currently we're
+@@ -884,6 +937,10 @@
+ lock(&mp.threadLock)
+ stdcall1(_CloseHandle, mp.thread)
+ mp.thread = 0
++ if mp.highResTimer != 0 {
++ stdcall1(_CloseHandle, mp.highResTimer)
++ mp.highResTimer = 0
++ }
+ unlock(&mp.threadLock)
+ }
+
+@@ -979,6 +1036,7 @@
+ // in sys_windows_386.s and sys_windows_amd64.s
+ func onosstack(fn unsafe.Pointer, arg uint32)
+ func usleep2(usec uint32)
++func usleep2HighRes(usec uint32)
+ func switchtothread()
+
+ var usleep2Addr unsafe.Pointer
+diff --git a/src/runtime/sys_windows_386.s b/src/runtime/sys_windows_386.s
+index 9e1f409..4ac1527 100644
+--- a/src/runtime/sys_windows_386.s
++++ b/src/runtime/sys_windows_386.s
+@@ -428,6 +428,42 @@
+ MOVL BP, SP
+ RET
+
++// Runs on OS stack. duration (in 100ns units) is in BX.
++TEXT runtime·usleep2HighRes(SB),NOSPLIT,$36
++ // Want negative 100ns units.
++ NEGL BX
++ MOVL $-1, hi-4(SP)
++ MOVL BX, lo-8(SP)
++
++ get_tls(CX)
++ MOVL g(CX), CX
++ MOVL g_m(CX), CX
++ MOVL (m_mOS+mOS_highResTimer)(CX), CX
++ MOVL CX, saved_timer-12(SP)
++
++ MOVL $0, fResume-16(SP)
++ MOVL $0, lpArgToCompletionRoutine-20(SP)
++ MOVL $0, pfnCompletionRoutine-24(SP)
++ MOVL $0, lPeriod-28(SP)
++ LEAL lo-8(SP), BX
++ MOVL BX, lpDueTime-32(SP)
++ MOVL CX, hTimer-36(SP)
++ MOVL SP, BP
++ MOVL runtime·_SetWaitableTimer(SB), AX
++ CALL AX
++ MOVL BP, SP
++
++ MOVL $0, ptime-28(SP)
++ MOVL $0, alertable-32(SP)
++ MOVL saved_timer-12(SP), CX
++ MOVL CX, handle-36(SP)
++ MOVL SP, BP
++ MOVL runtime·_NtWaitForSingleObject(SB), AX
++ CALL AX
++ MOVL BP, SP
++
++ RET
++
+ // Runs on OS stack.
+ TEXT runtime·switchtothread(SB),NOSPLIT,$0
+ MOVL SP, BP
+diff --git a/src/runtime/sys_windows_amd64.s b/src/runtime/sys_windows_amd64.s
+index 6c8eecd..8475425 100644
+--- a/src/runtime/sys_windows_amd64.s
++++ b/src/runtime/sys_windows_amd64.s
+@@ -457,6 +457,38 @@
+ MOVQ 40(SP), SP
+ RET
+
++// Runs on OS stack. duration (in 100ns units) is in BX.
++TEXT runtime·usleep2HighRes(SB),NOSPLIT|NOFRAME,$72
++ MOVQ SP, AX
++ ANDQ $~15, SP // alignment as per Windows requirement
++ MOVQ AX, 64(SP)
++
++ get_tls(CX)
++ MOVQ g(CX), CX
++ MOVQ g_m(CX), CX
++ MOVQ (m_mOS+mOS_highResTimer)(CX), CX // hTimer
++ MOVQ CX, 48(SP) // save hTimer for later
++ // Want negative 100ns units.
++ NEGQ BX
++ LEAQ 56(SP), DX // lpDueTime
++ MOVQ BX, (DX)
++ MOVQ $0, R8 // lPeriod
++ MOVQ $0, R9 // pfnCompletionRoutine
++ MOVQ $0, AX
++ MOVQ AX, 32(SP) // lpArgToCompletionRoutine
++ MOVQ AX, 40(SP) // fResume
++ MOVQ runtime·_SetWaitableTimer(SB), AX
++ CALL AX
++
++ MOVQ 48(SP), CX // handle
++ MOVQ $0, DX // alertable
++ MOVQ $0, R8 // ptime
++ MOVQ runtime·_NtWaitForSingleObject(SB), AX
++ CALL AX
++
++ MOVQ 64(SP), SP
++ RET
++
+ // Runs on OS stack.
+ TEXT runtime·switchtothread(SB),NOSPLIT|NOFRAME,$0
+ MOVQ SP, AX
+diff --git a/src/runtime/sys_windows_arm.s b/src/runtime/sys_windows_arm.s
+index 256b5ff..e6c61a4 100644
+--- a/src/runtime/sys_windows_arm.s
++++ b/src/runtime/sys_windows_arm.s
+@@ -468,6 +468,24 @@
+ MOVW R4, R13 // Restore SP
+ MOVM.IA.W (R13), [R4, R15] // pop {R4, pc}
+
++// Runs on OS stack. Duration (in 100ns units) is in R0.
++// TODO: neeeds to be implemented properly.
++TEXT runtime·usleep2HighRes(SB),NOSPLIT|NOFRAME,$0
++ MOVM.DB.W [R4, R14], (R13) // push {r4, lr}
++ MOVW R13, R4 // Save SP
++ SUB $8, R13 // R13 = R13 - 8
++ BIC $0x7, R13 // Align SP for ABI
++ RSB $0, R0, R3 // R3 = -R0
++ MOVW $0, R1 // R1 = FALSE (alertable)
++ MOVW $-1, R0 // R0 = handle
++ MOVW R13, R2 // R2 = pTime
++ MOVW R3, 0(R2) // time_lo
++ MOVW R0, 4(R2) // time_hi
++ MOVW runtime·_NtWaitForSingleObject(SB), R3
++ BL (R3)
++ MOVW R4, R13 // Restore SP
++ MOVM.IA.W (R13), [R4, R15] // pop {R4, pc}
++
+ // Runs on OS stack.
+ TEXT runtime·switchtothread(SB),NOSPLIT|NOFRAME,$0
+ MOVM.DB.W [R4, R14], (R13) // push {R4, lr}