Last active
February 4, 2021 00:45
-
-
Save omaskery/8313096dd475659d63297889cff1818c to your computer and use it in GitHub Desktop.
Investigating slowness of golang/go executable on Windows?
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
NOTE: pathext contains 11 extensions | |
NOTE: path has 50 entries | |
goos: windows | |
goarch: amd64 | |
pkg: golang-exec-slow-testbed | |
BenchmarkBuiltinUnknownCommand | |
BenchmarkBuiltinUnknownCommand-12 3 398999967 ns/op | |
BenchmarkBuiltinKnownCommand | |
BenchmarkBuiltinKnownCommand-12 218 5467874 ns/op | |
BenchmarkLocalCopyWithUnknownCommand | |
BenchmarkLocalCopyWithUnknownCommand-12 3 403333200 ns/op | |
BenchmarkLocalCopyWithKnownCommand | |
BenchmarkLocalCopyWithKnownCommand-12 222 5447606 ns/op | |
BenchmarkFindExecutableWithExistingFileButNoExtensions | |
BenchmarkFindExecutableWithExistingFileButNoExtensions-12 190485 6300 ns/op | |
BenchmarkFindExecutableWithExistingFileWithSomeExtensions | |
BenchmarkFindExecutableWithExistingFileWithSomeExtensions-12 184611 6636 ns/op | |
BenchmarkFindExecutableWithExistingFileWithRealExtensions | |
BenchmarkFindExecutableWithExistingFileWithRealExtensions-12 193545 6505 ns/op | |
BenchmarkFindExecutableWithNonExistingFileButNoExtensions | |
BenchmarkFindExecutableWithNonExistingFileButNoExtensions-12 3242 371683 ns/op | |
BenchmarkFindExecutableWithNonExistingFileWithSomeExtensions | |
BenchmarkFindExecutableWithNonExistingFileWithSomeExtensions-12 1071 1091504 ns/op | |
BenchmarkFindExecutableWithNonExistingFileWithRealExtensions | |
BenchmarkFindExecutableWithNonExistingFileWithRealExtensions-12 298 4073811 ns/op | |
BenchmarkFindExecutableOnExistingFileProportionalToPathLength | |
BenchmarkFindExecutableOnExistingFileProportionalToPathLength-12 3428 330804 ns/op | |
BenchmarkFindExecutableOnNonExistingFileProportionalToPathLength | |
BenchmarkFindExecutableOnNonExistingFileProportionalToPathLength-12 5 204799720 ns/op | |
BenchmarkStatOnExistingFile | |
BenchmarkStatOnExistingFile-12 187501 6240 ns/op | |
BenchmarkStatOnNonExistingFile | |
BenchmarkStatOnNonExistingFile-12 3242 376073 ns/op | |
PASS | |
Process finished with exit code 0 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
NOTE: pathext contains 0 extensions | |
NOTE: path has 57 entries | |
wrote CPU profile to builtin_known.prof | |
wrote CPU profile to builtin_unknown.prof | |
goos: linux | |
goarch: amd64 | |
pkg: golang-exec-slow-testbed | |
BenchmarkBuiltinUnknownCommand-12 4 288830750 ns/op | |
BenchmarkBuiltinKnownCommand-12 248312 5268 ns/op | |
BenchmarkLocalCopyWithUnknownCommand-12 4 291618275 ns/op | |
BenchmarkLocalCopyWithKnownCommand-12 240192 5041 ns/op | |
BenchmarkFindExecutableWithExistingFileButNoExtensions-12 573 2075049 ns/op | |
BenchmarkFindExecutableWithExistingFileWithSomeExtensions-12 582 2137966 ns/op | |
BenchmarkFindExecutableWithExistingFileWithRealExtensions-12 579 2034148 ns/op | |
BenchmarkFindExecutableWithNonExistingFileButNoExtensions-12 2415 479138 ns/op | |
BenchmarkFindExecutableWithNonExistingFileWithSomeExtensions-12 2637 476540 ns/op | |
BenchmarkFindExecutableWithNonExistingFileWithRealExtensions-12 2622 472515 ns/op | |
BenchmarkFindExecutableOnExistingFileProportionalToPathLength-12 9 115634533 ns/op | |
BenchmarkFindExecutableOnNonExistingFileProportionalToPathLength-12 45 27054131 ns/op | |
BenchmarkStatOnExistingFile-12 596 2033425 ns/op | |
BenchmarkStatOnNonExistingFile-12 2502 473269 ns/op | |
PASS | |
ok golang-exec-slow-testbed 20.892s |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// +build linux | |
// THIS IS COPIED FROM GO STD LIBRARY | |
package main | |
import ( | |
"errors" | |
"os" | |
"path/filepath" | |
"strings" | |
"os/exec" | |
) | |
var ErrNotFound = errors.New("executable file not found in $PATH") | |
// bodged to have same signature as the Windows implementation for test purposes | |
func findExecutable(file string, exts []string) (string, error) { | |
_ = exts | |
d, err := os.Stat(file) | |
if err != nil { | |
return "", err | |
} | |
if m := d.Mode(); !m.IsDir() && m&0111 != 0 { | |
return "", nil | |
} | |
return "", os.ErrPermission | |
} | |
func LookPath(file string) (string, error) { | |
if strings.Contains(file, "/") { | |
_, err := findExecutable(file, nil) | |
if err == nil { | |
return file, nil | |
} | |
return "", &exec.Error{file, err} | |
} | |
path := os.Getenv("PATH") | |
for _, dir := range filepath.SplitList(path) { | |
if dir == "" { | |
// Unix shell semantics: path element "" means "." | |
dir = "." | |
} | |
path := filepath.Join(dir, file) | |
if _, err := findExecutable(path, nil); err == nil { | |
return path, nil | |
} | |
} | |
return "", &exec.Error{file, ErrNotFound} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package main | |
import ( | |
"fmt" | |
"os" | |
"os/exec" | |
"path/filepath" | |
"runtime" | |
"runtime/pprof" | |
"testing" | |
) | |
var ( | |
pathExt = os.Getenv("PATHEXT") | |
realExtList = filepath.SplitList(pathExt) | |
shortExtList []string | |
rawPath = os.Getenv("PATH") | |
splitPath = filepath.SplitList(rawPath) | |
pathLength = len(splitPath) | |
profiledRepetitions = 1 | |
knownCommand string | |
unknownCommand = "made-up-command" | |
existingFile = "lookpath_test.go" | |
nonExistingFile = "doesnt-exist" | |
) | |
func init() { | |
fmt.Printf("NOTE: pathext contains %v extensions\n", len(realExtList)) | |
fmt.Printf("NOTE: path has %v entries\n", pathLength) | |
if len(realExtList) > 3 { | |
shortExtList = realExtList[:3] | |
} | |
if runtime.GOOS == "windows" { | |
knownCommand = "cmd" | |
} else { | |
knownCommand = "ls" | |
} | |
} | |
func BenchmarkBuiltinUnknownCommand(b *testing.B) { | |
for i := 0; i < b.N; i++ { | |
_, _ = exec.LookPath(unknownCommand) | |
} | |
} | |
func BenchmarkBuiltinKnownCommand(b *testing.B) { | |
for i := 0; i < b.N; i++ { | |
_, _ = exec.LookPath(knownCommand) | |
} | |
} | |
func TestProfileBuiltin(b *testing.T) { | |
profiled("builtin_known.prof", func() { | |
for i := 0; i < profiledRepetitions; i++ { | |
_, _ = exec.LookPath(knownCommand) | |
} | |
}) | |
profiled("builtin_unknown.prof", func() { | |
for i := 0; i < profiledRepetitions; i++ { | |
_, _ = exec.LookPath(unknownCommand) | |
} | |
}) | |
} | |
func BenchmarkLocalCopyWithUnknownCommand(b *testing.B) { | |
for i := 0; i < b.N; i++ { | |
_, _ = LookPath(unknownCommand) | |
} | |
} | |
func BenchmarkLocalCopyWithKnownCommand(b *testing.B) { | |
for i := 0; i < b.N; i++ { | |
_, _ = LookPath(knownCommand) | |
} | |
} | |
func BenchmarkFindExecutableWithExistingFileButNoExtensions(b *testing.B) { | |
for i := 0; i < b.N; i++ { | |
_, _ = findExecutable(existingFile, nil) | |
} | |
} | |
func BenchmarkFindExecutableWithExistingFileWithSomeExtensions(b *testing.B) { | |
for i := 0; i < b.N; i++ { | |
_, _ = findExecutable(existingFile, shortExtList) | |
} | |
} | |
func BenchmarkFindExecutableWithExistingFileWithRealExtensions(b *testing.B) { | |
for i := 0; i < b.N; i++ { | |
_, _ = findExecutable(existingFile, realExtList) | |
} | |
} | |
func BenchmarkFindExecutableWithNonExistingFileButNoExtensions(b *testing.B) { | |
for i := 0; i < b.N; i++ { | |
_, _ = findExecutable(nonExistingFile, nil) | |
} | |
} | |
func BenchmarkFindExecutableWithNonExistingFileWithSomeExtensions(b *testing.B) { | |
for i := 0; i < b.N; i++ { | |
_, _ = findExecutable(nonExistingFile, shortExtList) | |
} | |
} | |
func BenchmarkFindExecutableWithNonExistingFileWithRealExtensions(b *testing.B) { | |
for i := 0; i < b.N; i++ { | |
_, _ = findExecutable(nonExistingFile, realExtList) | |
} | |
} | |
func BenchmarkFindExecutableOnExistingFileProportionalToPathLength(b *testing.B) { | |
for i := 0; i < b.N; i++ { | |
for i := 0; i < pathLength; i++ { | |
_, _ = findExecutable(existingFile, realExtList) | |
} | |
} | |
} | |
func BenchmarkFindExecutableOnNonExistingFileProportionalToPathLength(b *testing.B) { | |
for i := 0; i < b.N; i++ { | |
for i := 0; i < pathLength; i++ { | |
_, _ = findExecutable(nonExistingFile, realExtList) | |
} | |
} | |
} | |
func BenchmarkStatOnExistingFile(b *testing.B) { | |
for i := 0; i < b.N; i++ { | |
_, _ = os.Stat(existingFile) | |
} | |
} | |
func BenchmarkStatOnNonExistingFile(b *testing.B) { | |
for i := 0; i < b.N; i++ { | |
_, _ = os.Stat(nonExistingFile) | |
} | |
} | |
func profiled(path string, f func()) { | |
file, err := os.Create(path) | |
if err != nil { | |
panic(fmt.Sprintf("failed to create CPU profile file: %v", err)) | |
} | |
if err := pprof.StartCPUProfile(file); err != nil { | |
panic(fmt.Sprintf("failed to start CPU profile: %v", err)) | |
} | |
defer pprof.StopCPUProfile() | |
f() | |
fmt.Printf("wrote CPU profile to %s\n", path) | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// +build windows | |
// THIS IS COPIED FROM GO STD LIBRARY | |
package main | |
import ( | |
"errors" | |
"os" | |
"os/exec" | |
"path/filepath" | |
"strings" | |
) | |
func LookPath(file string) (string, error) { | |
var exts []string | |
x := os.Getenv(`PATHEXT`) | |
if x != "" { | |
for _, e := range strings.Split(strings.ToLower(x), `;`) { | |
if e == "" { | |
continue | |
} | |
if e[0] != '.' { | |
e = "." + e | |
} | |
exts = append(exts, e) | |
} | |
} else { | |
exts = []string{".com", ".exe", ".bat", ".cmd"} | |
} | |
if strings.ContainsAny(file, `:\/`) { | |
if f, err := findExecutable(file, exts); err == nil { | |
return f, nil | |
} else { | |
return "", &exec.Error{Name: file, Err: err} | |
} | |
} | |
if f, err := findExecutable(filepath.Join(".", file), exts); err == nil { | |
return f, nil | |
} | |
path := os.Getenv("path") | |
for _, dir := range filepath.SplitList(path) { | |
if f, err := findExecutable(filepath.Join(dir, file), exts); err == nil { | |
return f, nil | |
} | |
} | |
return "", &exec.Error{Name: file, Err: ErrNotFound} | |
} | |
func findExecutable(file string, exts []string) (string, error) { | |
if len(exts) == 0 { | |
return file, chkStat(file) | |
} | |
if hasExt(file) { | |
if chkStat(file) == nil { | |
return file, nil | |
} | |
} | |
for _, e := range exts { | |
if f := file + e; chkStat(f) == nil { | |
return f, nil | |
} | |
} | |
return "", os.ErrNotExist | |
} | |
func chkStat(file string) error { | |
d, err := os.Stat(file) | |
if err != nil { | |
return err | |
} | |
if d.IsDir() { | |
return os.ErrPermission | |
} | |
return nil | |
} | |
func hasExt(file string) bool { | |
i := strings.LastIndex(file, ".") | |
if i < 0 { | |
return false | |
} | |
return strings.LastIndexAny(file, `:\/`) < i | |
} | |
var ErrNotFound = errors.New("executable file not found in %PATH%") |
NOTE: pathext contains 0 extensions
NOTE: path has 14 entries
wrote CPU profile to builtin_known.prof
wrote CPU profile to builtin_unknown.prof
goos: linux
goarch: amd64
BenchmarkBuiltinUnknownCommand-24 53644 22541 ns/op
BenchmarkBuiltinKnownCommand-24 148549 7621 ns/op
BenchmarkLocalCopyWithUnknownCommand-24 55334 21462 ns/op
BenchmarkLocalCopyWithKnownCommand-24 156183 8003 ns/op
BenchmarkFindExecutableWithExistingFileButNoExtensions-24 1000000 1054 ns/op
BenchmarkFindExecutableWithExistingFileWithSomeExtensions-24 1000000 1054 ns/op
BenchmarkFindExecutableWithExistingFileWithRealExtensions-24 1000000 1018 ns/op
BenchmarkFindExecutableWithNonExistingFileButNoExtensions-24 1479615 841 ns/op
BenchmarkFindExecutableWithNonExistingFileWithSomeExtensions-24 1435975 840 ns/op
BenchmarkFindExecutableWithNonExistingFileWithRealExtensions-24 1445222 820 ns/op
BenchmarkFindExecutableOnExistingFileProportionalToPathLength-24 83017 14673 ns/op
BenchmarkFindExecutableOnNonExistingFileProportionalToPathLength-24 103734 11680 ns/op
BenchmarkStatOnExistingFile-24 1000000 1038 ns/op
BenchmarkStatOnNonExistingFile-24 1444682 825 ns/op
PASS
ok _/home/ricky26/Projects/OSS/temp/8313096dd475659d63297889cff1818c-00532cebdad0e3bf7ea8438777bfc1fe1a1037e3 20.887s
This turned out to be an antivirus-like piece of software called Trusteer Endpoint Security (sometimes called Rapport) by IBM.
This turned out to be an antivirus-like piece of software called Trusteer Endpoint Security (sometimes called Rapport) by IBM.
Yep I've got that installed. How did you figure it out?
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Here's my output: