-
Notifications
You must be signed in to change notification settings - Fork 18.5k
Description
Go version
go version devel go1.22-b702e0438a Mon Jan 8 20:15:52 2024 +0000 darwin/arm64
Output of go env in your module/workspace:
GO111MODULE=''
GOARCH='arm64'
GOBIN=''
GOCACHE='/Users/thanm/Library/Caches/go-build'
GOENV='/Users/thanm/Library/Application Support/go/env'
GOEXE=''
GOEXPERIMENT=''
GOFLAGS=''
GOHOSTARCH='arm64'
GOHOSTOS='darwin'
GOINSECURE=''
GOMODCACHE='/Users/thanm/go1/pkg/mod'
GONOPROXY=''
GONOSUMDB=''
GOOS='darwin'
GOPATH='/Users/thanm/go1'
GOPRIVATE=''
GOPROXY='https://proxy.golang.org,direct'
GOROOT='/Users/thanm/go'
GOSUMDB='sum.golang.org'
GOTMPDIR=''
GOTOOLCHAIN='auto'
GOTOOLDIR='/Users/thanm/go/pkg/tool/darwin_arm64'
GOVCS=''
GOVERSION='devel go1.22-b702e0438a Mon Jan 8 20:15:52 2024 +0000'
GCCGO='gccgo'
AR='ar'
CC='clang'
CXX='clang++'
CGO_ENABLED='1'
GOMOD='/Users/thanm/go/src/go.mod'
GOWORK=''
CGO_CFLAGS='-O2 -g'
CGO_CPPFLAGS=''
CGO_CXXFLAGS='-O2 -g'
CGO_FFLAGS='-O2 -g'
CGO_LDFLAGS='-O2 -g'
PKG_CONFIG='pkg-config'
GOGCCFLAGS='-fPIC -arch arm64 -pthread -fno-caret-diagnostics -Qunused-arguments -fmessage-length=0 -ffile-prefix-map=/var/folders/tf/lh5xw2y1657fwx2q0vxrt_5m008r__/T/go-build1807409156=/tmp/go-build -gno-record-gcc-switches -fno-common'What did you do?
Build this program:
https://go.dev/play/p/6Jsq0rUhQ9K
Source:
package main
import (
"os/exec"
"slices"
)
func main() {
var s []exec.Cmd
s = slices.Insert(s, 0, exec.Cmd{})
}
and examine the assembly (gcflags=-S).
What did you see happen?
At the call to slices.Insert in main function, here is an assembly fragment (from "go build -gcflags=-S -trimpath main.go"):
0x0054 00084 (./main.go:10) MOVD $1, R6
0x0058 00088 (./main.go:10) MOVD R6, R7
0x005c 00092 (./main.go:10) PCDATA $1, $0
0x005c 00092 (./main.go:10) CALL slices.Insert[go.shape.[]os/exec.Cmd,go.shape.struct { Path string; Args []string; Env []string; Dir string; Stdin io.Reader; Stdout io.Writer; Stderr io.Writer; ExtraFiles []*os.File; SysProcAttr *syscall.SysProcAttr; Process *os.Process; ProcessState *os.ProcessState; os/exec.ctx context.Context; Err error; Cancel func() error; WaitDelay time.Duration; os/exec.childIOFiles []io.Closer; os/exec.parentIOPipes []io.Closer; os/exec.goroutine []func() error; os/exec.goroutineErr <-chan error; os/exec.ctxResult <-chan os/exec.ctxResult; os/exec.createdByStack []uint8; os/exec.lookPathErr error }](SB)
0x0060 00096 (./main.go:11) LDP -8(RSP), (R29, R30)
0x0064 00100 (./main.go:11) ADD $432, RSP
The program works properly, but note the name for the instantiated version of slices.Insert, which is almost 600 characters long. Of particular concern is that although the generic function is instantiated with the type exec.Cmd, the function name looks like it refers to an anonymous struct type that has all the fields of cmd.Exec, but in which all the field names/types are spelled out (including package paths, etc).
In this example, the long function name is not that big a deal, but creates needless binary bloat, and for profilers (ex: pprof) and other tools that capture data files based on function names, the long names can result in much bigger data files.
More importantly however if you have a package with very long paths, and you import a large struct type from that package and instantiate a generic based on that, you can get absurdly long function names.
What did you expect to see?
Ideally it would be better to have generic functions names that are proportional in size to the length of their qualified type parameters (e.g. "os/exec.Cmd") as opposed to O(N * P) where N is the number of struct fields in the type param and P is the length of the import path of the type's package.