syscall: cache Errno.Error() on Windows

Windows is unlike the other OSs and depends on a syscall for most
errors. This can be costly; cache the returned string for later reuse.

This helps test caching, since errors are written out as string to the
test ID, which are often PathErrors wrapping Errnos.

For now, only cache ERROR_FILE_NOT_FOUND and ERROR_PATH_NOT_FOUND.

goos: windows
goarch: amd64
pkg: syscall
cpu: Intel(R) Core(TM) i9-10900K CPU @ 3.70GHz
               │    old.txt    │               new.txt               │
               │    sec/op     │   sec/op     vs base                │
ErrnoString-20   1788.00n ± 1%   11.08n ± 1%  -99.38% (p=0.000 n=10)

               │  old.txt   │              new.txt               │
               │    B/op    │   B/op     vs base                 │
ErrnoString-20   48.00 ± 0%   0.00 ± 0%  -100.00% (p=0.000 n=10)

               │  old.txt   │               new.txt               │
               │ allocs/op  │ allocs/op   vs base                 │
ErrnoString-20   1.000 ± 0%   0.000 ± 0%  -100.00% (p=0.000 n=10)

For #72992

Change-Id: I9a0910fa6538772ffc64ef7670b44059a2c7d18c
Reviewed-on: https://go-review.googlesource.com/c/go/+/667495
Reviewed-by: Carlos Amedee <carlos@golang.org>
LUCI-TryBot-Result: Go LUCI <golang-scoped@luci-project-accounts.iam.gserviceaccount.com>
Reviewed-by: Alex Brainman <alex.brainman@gmail.com>
Reviewed-by: Quim Muntal <quimmuntal@gmail.com>
Reviewed-by: Junyang Shao <shaojunyang@google.com>
Auto-Submit: Carlos Amedee <carlos@golang.org>
This commit is contained in:
Jake Bailey 2025-03-24 16:01:06 -07:00 committed by Gopher Robot
parent c966f1c0c0
commit 06751c455d
2 changed files with 27 additions and 0 deletions

View File

@ -138,12 +138,32 @@ func FormatMessage(flags uint32, msgsrc uint32, msgid uint32, langid uint32, buf
return formatMessage(flags, uintptr(msgsrc), msgid, langid, buf, args)
}
var errnoErrorCache sync.Map
func (e Errno) Error() string {
// deal with special go errors
idx := int(e - APPLICATION_ERROR)
if 0 <= idx && idx < len(errors) {
return errors[idx]
}
cache := false
switch e {
case ERROR_FILE_NOT_FOUND, ERROR_PATH_NOT_FOUND:
if cached, ok := errnoErrorCache.Load(e); ok {
return cached.(string)
}
cache = true
}
result := e.error()
if cache {
errnoErrorCache.Store(e, result)
}
return result
}
func (e Errno) error() string {
// ask windows for the remaining errors
var flags uint32 = FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_ARGUMENT_ARRAY | FORMAT_MESSAGE_IGNORE_INSERTS
b := make([]uint16, 300)

View File

@ -299,3 +299,10 @@ func FuzzUTF16FromString(f *testing.F) {
}
})
}
func BenchmarkErrnoString(b *testing.B) {
b.ReportAllocs()
for b.Loop() {
_ = syscall.Errno(2).Error()
}
}