Skip to content

Instantly share code, notes, and snippets.

@omaskery
Last active February 4, 2021 00:45
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save omaskery/8313096dd475659d63297889cff1818c to your computer and use it in GitHub Desktop.
Save omaskery/8313096dd475659d63297889cff1818c to your computer and use it in GitHub Desktop.
Investigating slowness of golang/go executable on Windows?
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
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
// +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}
}
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)
}
// +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%")
@servusdei2018
Copy link

servusdei2018 commented Feb 4, 2021

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