version: use debug.ReadBuildInfo in CmdName on non-Windows
CmdName was re-opening the running executable and scanning it in
64KiB chunks for the Go modinfo markers on every call. The same
modinfo is already parsed at startup and exposed via
runtime/debug.ReadBuildInfo, so prefer that on non-Windows. Windows
still takes the scanning path because its GUI-binary override keys
off the on-disk executable name.
benchstat of BenchmarkCmdName (Linux, before vs after):
goos: linux
goarch: amd64
pkg: tailscale.com/version
cpu: Intel(R) Xeon(R) 6975P-C
│ /tmp/old.txt │ /tmp/new.txt │
│ sec/op │ sec/op vs base │
CmdName-16 556045.5n ± 1% 825.6n ± 1% -99.85% (p=0.000 n=10)
│ /tmp/old.txt │ /tmp/new.txt │
│ B/op │ B/op vs base │
CmdName-16 64.587Ki ± 0% 1.156Ki ± 0% -98.21% (p=0.000 n=10)
│ /tmp/old.txt │ /tmp/new.txt │
│ allocs/op │ allocs/op vs base │
CmdName-16 8.000 ± 0% 7.000 ± 0% -12.50% (p=0.000 n=10)
Fixes #19486
Change-Id: I925c5e28b64815a602459beb6c8dab8779339a6c
Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
This commit is contained in:
committed by
Brad Fitzpatrick
parent
306fab796c
commit
006d7e180e
@@ -13,6 +13,7 @@ import (
|
|||||||
"os"
|
"os"
|
||||||
"path"
|
"path"
|
||||||
"runtime"
|
"runtime"
|
||||||
|
"runtime/debug"
|
||||||
"strings"
|
"strings"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -20,6 +21,15 @@ import (
|
|||||||
// using os.Executable. If os.Executable fails (it shouldn't), then
|
// using os.Executable. If os.Executable fails (it shouldn't), then
|
||||||
// "cmd" is returned.
|
// "cmd" is returned.
|
||||||
func CmdName() string {
|
func CmdName() string {
|
||||||
|
// On non-Windows, the modinfo embedded in the running binary is
|
||||||
|
// authoritative and avoids re-reading the executable from disk.
|
||||||
|
// Windows needs the executable-name-based GUI override in cmdName,
|
||||||
|
// so it still takes the slower path.
|
||||||
|
if runtime.GOOS != "windows" {
|
||||||
|
if info, ok := debug.ReadBuildInfo(); ok && info.Path != "" {
|
||||||
|
return path.Base(info.Path)
|
||||||
|
}
|
||||||
|
}
|
||||||
e, err := os.Executable()
|
e, err := os.Executable()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "cmd"
|
return "cmd"
|
||||||
|
|||||||
@@ -6,6 +6,8 @@ package version_test
|
|||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"os"
|
"os"
|
||||||
|
"path"
|
||||||
|
"runtime/debug"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
ts "tailscale.com"
|
ts "tailscale.com"
|
||||||
@@ -49,3 +51,21 @@ func TestShortAllocs(t *testing.T) {
|
|||||||
t.Errorf("allocs = %v; want 0", allocs)
|
t.Errorf("allocs = %v; want 0", allocs)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func BenchmarkCmdName(b *testing.B) {
|
||||||
|
b.ReportAllocs()
|
||||||
|
for b.Loop() {
|
||||||
|
_ = version.CmdName()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func BenchmarkReadBuildInfo(b *testing.B) {
|
||||||
|
b.ReportAllocs()
|
||||||
|
for b.Loop() {
|
||||||
|
info, ok := debug.ReadBuildInfo()
|
||||||
|
if !ok {
|
||||||
|
b.Fatal("ReadBuildInfo failed")
|
||||||
|
}
|
||||||
|
_ = path.Base(info.Path)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user