github.com/benz9527/xboot@v0.0.0-20240504061247-c23f15593274/lib/hrtime/time_windows.go (about) 1 //go:build windows 2 // +build windows 3 4 // High-resolution time for Windows. 5 6 package hrtime 7 8 // References: 9 // https://github.com/golang/go/issues/31160 10 // https://github.com/golang/go/issues/31528 11 // https://go-review.googlesource.com/c/go/+/227499/1/src/testing/time_windows.go 12 // https://go-review.googlesource.com/c/sys/+/515915 13 // https://github.com/golang/go/blob/master/src/runtime/os_windows.go#L447 14 // https://github.com/golang/go/blob/9b6e9f0c8c66355c0f0575d808b32f52c8c6d21c/src/runtime/os_windows.go#L381 15 // https://learn.microsoft.com/en-us/windows/win32/sysinfo/acquiring-high-resolution-time-stamps 16 17 import ( 18 "errors" 19 "sync/atomic" 20 "time" 21 "unsafe" 22 23 "golang.org/x/sys/windows" 24 ) 25 26 // Way 1: Update the Windows time resolution to 1ms. 27 28 var ( 29 windowsHighResolutionEnabled = atomic.Bool{} 30 ) 31 32 func SetTimeResolutionTo1ms() { 33 if windowsHighResolutionEnabled.Load() { 34 return 35 } 36 if err := windows.TimeBeginPeriod(1); err != nil { 37 panic(err) 38 } 39 windowsHighResolutionEnabled.Store(true) 40 } 41 42 func ResetTimeResolutionFrom1ms() error { 43 if !windowsHighResolutionEnabled.Load() { 44 return nil 45 } 46 if err := windows.TimeEndPeriod(1); err != nil { 47 return err 48 } 49 windowsHighResolutionEnabled.Store(false) 50 return nil 51 } 52 53 // Way 2: Use the Windows high-resolution performance counter API. 54 55 var ( 56 // Load windows dynamic link library. 57 kernel32 = windows.NewLazyDLL("kernel32.dll") 58 // Find windows dynamic link library functions. 59 procQPF = kernel32.NewProc("QueryPerformanceFrequency") 60 procQPC = kernel32.NewProc("QueryPerformanceCounter") 61 ) 62 63 // https://learn.microsoft.com/en-us/windows/win32/api/profileapi/nf-profileapi-queryperformancefrequency 64 // BOOL QueryPerformanceFrequency( 65 // 66 // [out] LARGE_INTEGER *lpFrequency 67 // 68 // ); 69 func getFrequency() (int64, bool) { 70 var freq int64 71 r1, _, err := procQPF.Call(uintptr(unsafe.Pointer(&freq))) 72 if err != nil && !errors.Is(err, windows.SEVERITY_SUCCESS) { 73 panic(err) 74 } 75 return freq, r1 == 1 76 } 77 78 // https://learn.microsoft.com/en-us/windows/win32/api/profileapi/nf-profileapi-queryperformancecounter 79 // BOOL QueryPerformanceCounter( 80 // 81 // [out] LARGE_INTEGER *lpPerformanceCount 82 // 83 // ); 84 // In multi-cores CPU, the counter may not be monotonic. 85 // 1. Hardware differences: The counter is not guaranteed to be consistent across different hardware. 86 // 2. CPU cache-line consistency: The counter is not guaranteed to be consistent across different CPU cores. 87 // 3. Interrupt delays: The counter is not guaranteed to be consistent across different CPU cores. 88 // 4. (Power management) Dynamic Voltage and Frequency Scaling (DVFS): The counter is not guaranteed to be consistent across different CPU cores. 89 // 5. OS scheduling (CPU Core-to-Core): The counter is not guaranteed to be consistent across different CPU cores. 90 // 6. Some BIOS issues in multi-cores CPU's: The counter is not guaranteed to be consistent across different CPU cores. 91 // The way to fetch precious counter-number is running on a single-core CPU. 92 func getCounter() (int64, bool) { 93 var counter int64 94 r1, _, err := procQPC.Call(uintptr(unsafe.Pointer(&counter))) 95 if err != nil && !errors.Is(err, windows.SEVERITY_SUCCESS) { 96 panic(err) 97 } 98 return counter, r1 == 1 99 } 100 101 var ( 102 baseProcFreq int64 103 baseProcCounter int64 104 fallbackLowResolution atomic.Bool 105 ) 106 107 func SetFallbackLowResolution(flag bool) { 108 fallbackLowResolution.Swap(flag) 109 } 110 111 func ClockInit() { 112 SetTimeResolutionTo1ms() 113 appStartTime = time.Now().In(loadTZLocation(TimeZoneOffset(atomic.LoadInt32(&defaultTimezoneOffset)))) 114 115 var ok bool 116 if baseProcCounter, ok = getCounter(); !ok { 117 fallbackLowResolution.Store(true) 118 } 119 if baseProcFreq, ok = getFrequency(); !fallbackLowResolution.Load() && !ok || baseProcFreq <= 0 { 120 fallbackLowResolution.Store(true) 121 } 122 } 123 124 func now() time.Time { 125 nano := appStartTime.UnixNano() + MonotonicElapsed().Nanoseconds() 126 return time.UnixMilli(time.Duration(nano).Milliseconds()) 127 } 128 129 func NowIn(offset TimeZoneOffset) time.Time { 130 if fallbackLowResolution.Load() { 131 return time.Now().In(loadTZLocation(offset)) 132 } 133 return now().In(loadTZLocation(offset)) 134 } 135 136 func NowInDefaultTZ() time.Time { 137 return NowIn(TimeZoneOffset(atomic.LoadInt32(&defaultTimezoneOffset))) 138 } 139 140 func NowInUTC() time.Time { 141 return NowIn(TzUtc0Offset) 142 } 143 144 // MonotonicElapsed returns the time elapsed since the program started. 145 // Note: Not a very precise implementation. 146 func MonotonicElapsed() time.Duration { 147 if fallbackLowResolution.Load() { 148 return time.Since(appStartTime) 149 } 150 currentCounter, _ := getCounter() 151 return time.Duration(currentCounter-baseProcCounter) * time.Second / (time.Duration(baseProcFreq) * time.Nanosecond) 152 } 153 154 func Since(beginTime time.Time) time.Duration { 155 if fallbackLowResolution.Load() { 156 return time.Since(beginTime) 157 } 158 n := NowInDefaultTZ() 159 return time.Duration(n.Nanosecond() - beginTime.In(loadTZLocation(TimeZoneOffset(DefaultTimezoneOffset()))).Nanosecond()) 160 } 161 162 var ( 163 SdkClock = &sdkClockTime{} 164 WindowsClock = &windowsClockTime{} 165 ) 166 167 type sdkClockTime struct{} 168 169 func (s *sdkClockTime) NowIn(offset TimeZoneOffset) time.Time { 170 SetFallbackLowResolution(true) 171 return NowIn(offset) 172 } 173 174 func (s *sdkClockTime) NowInDefaultTZ() time.Time { 175 SetFallbackLowResolution(true) 176 return s.NowIn(TimeZoneOffset(atomic.LoadInt32(&defaultTimezoneOffset))) 177 } 178 179 func (s *sdkClockTime) NowInUTC() time.Time { 180 SetFallbackLowResolution(true) 181 return s.NowIn(TzUtc0Offset) 182 } 183 184 func (s *sdkClockTime) MonotonicElapsed() time.Duration { 185 SetFallbackLowResolution(true) 186 return MonotonicElapsed() 187 } 188 189 func (s *sdkClockTime) Since(beginTime time.Time) time.Duration { 190 SetFallbackLowResolution(true) 191 return Since(beginTime) 192 } 193 194 type windowsClockTime struct{} 195 196 func (w *windowsClockTime) NowIn(offset TimeZoneOffset) time.Time { 197 SetFallbackLowResolution(false) 198 return NowIn(offset) 199 } 200 201 func (w *windowsClockTime) NowInDefaultTZ() time.Time { 202 SetFallbackLowResolution(false) 203 return w.NowIn(TimeZoneOffset(atomic.LoadInt32(&defaultTimezoneOffset))) 204 } 205 206 func (w *windowsClockTime) NowInUTC() time.Time { 207 SetFallbackLowResolution(false) 208 return w.NowIn(TzUtc0Offset) 209 } 210 211 func (w *windowsClockTime) MonotonicElapsed() time.Duration { 212 SetFallbackLowResolution(false) 213 return MonotonicElapsed() 214 } 215 216 func (w *windowsClockTime) Since(beginTime time.Time) time.Duration { 217 SetFallbackLowResolution(false) 218 return Since(beginTime) 219 }