golang / go

The Go programming language
https://go.dev
BSD 3-Clause "New" or "Revised" License
123.72k stars 17.62k forks source link

cmd/compile: binary size increase from generics #70045

Closed andig closed 2 hours ago

andig commented 3 hours ago

What version of Go are you using (go version)?

$ go version
go version go1.23.2 darwin/arm64

Does this issue reproduce with the latest release?

yes

What operating system and processor architecture are you using (go env)?

go env Output
$ go env
GO111MODULE=''
GOARCH='arm64'
GOBIN=''
GOCACHE='/Users/andig/Library/Caches/go-build'
GOENV='/Users/andig/Library/Application Support/go/env'
GOEXE=''
GOEXPERIMENT=''
GOFLAGS=''
GOHOSTARCH='arm64'
GOHOSTOS='darwin'
GOINSECURE=''
GOMODCACHE='/Users/andig/go/pkg/mod'
GONOPROXY=''
GONOSUMDB=''
GOOS='darwin'
GOPATH='/Users/andig/go'
GOPRIVATE=''
GOPROXY='https://proxy.golang.org,direct'
GOROOT='/opt/homebrew/Cellar/go/1.23.2/libexec'
GOSUMDB='sum.golang.org'
GOTMPDIR=''
GOTOOLCHAIN='local'
GOTOOLDIR='/opt/homebrew/Cellar/go/1.23.2/libexec/pkg/tool/darwin_arm64'
GOVCS=''
GOVERSION='go1.23.2'
GODEBUG=''
GOTELEMETRY='on'
GOTELEMETRYDIR='/Users/andig/Library/Application Support/go/telemetry'
GCCGO='gccgo'
GOARM64='v8.0'
AR='ar'
CC='cc'
CXX='c++'
CGO_ENABLED='1'
GOMOD='/Users/andig/htdocs/evcc/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/sv/rs_453y57xj86xsbz3kw1mbc0000gn/T/go-build1030719432=/tmp/go-build -gno-record-gcc-switches -fno-common'
GOROOT/bin/go version: go version go1.23.2 darwin/arm64
GOROOT/bin/go tool compile -V: compile version go1.23.2
uname -v: Darwin Kernel Version 24.0.0: Tue Sep 24 23:36:26 PDT 2024; root:xnu-11215.1.12~1/RELEASE_ARM64_T8103
ProductName:        macOS
ProductVersion:     15.0.1
BuildVersion:       24A348
lldb --version: lldb-1600.0.36.3
Apple Swift version 6.0 (swiftlang-6.0.0.9.10 clang-1600.0.26.2)

What did you do?

Replace for loop with generic function.

Before:

func (vv *vehicles) Instances() []api.Vehicle {
    devs := config.Vehicles().Devices()

    res := make([]api.Vehicle, 0, len(devs))
    for _, dev := range devs {
        res = append(res, dev.Instance())
    }

    return res
}

After:

func (vv *vehicles) Instances() []api.Vehicle {
    return lo.Map(config.Vehicles().Devices(), func(dev config.Device[api.Vehicle], _ int) api.Vehicle {
        return dev.Instance()
    })
}

with lo.Map simply being (samber/lo):

func Map[T any, R any](collection []T, iteratee func(item T, index int) R) []R {
    result := make([]R, len(collection))

    for i := range collection {
        result[i] = iteratee(collection[i], i)
    }

    return result
}

What did you expect to see?

Similar or smaller binary size

What did you see instead?

Binary size increased by 14kib for a single function call.

Before:

97417010

After:

97433586
gabyhelp commented 3 hours ago

Related Issues and Documentation

(Emoji vote if this was helpful or unhelpful; more detailed feedback welcome in this discussion.)

seankhliao commented 3 hours ago

do you have a self contained reproducer?

andig commented 2 hours ago

The repo is open source but a bit complex. Synthetic example yields additional 400 bytes, not sure that's relevant?

package main

import _ "github.com/samber/lo"

type element struct {
    value string
}

func (c *element) Value() string {
    return c.value
}

func main() {
    var list []element

    // 2953986 bytes
    res := make([]string, 0, len(list))
    for _, el := range list {
        res = append(res, el.Value())
    }

    // 2954306 bytes
    // res := lo.Map(list, func(el element, _ int) string {
    //  return el.Value()
    // })

    _ = res
}

Could I produce any kind of export data that might be helpful?

andig commented 2 hours ago

Another attempt (replacing main.go of https://github.com/evcc-io/evcc):

package main

import (
    "github.com/evcc-io/evcc/api"
    "github.com/evcc-io/evcc/util/config"
    "github.com/samber/lo"
)

type vehicles struct{}

// 9960802 bytes
// func (vv *vehicles) Instances() []api.Vehicle {
//  devs := config.Vehicles().Devices()

//  res := make([]api.Vehicle, 0, len(devs))
//  for _, dev := range devs {
//      res = append(res, dev.Instance())
//  }

//  return res
// }

// 9961538 bytes
func (vv *vehicles) Instances() []api.Vehicle {
    return lo.Map(config.Vehicles().Devices(), func(dev config.Device[api.Vehicle], _ int) api.Vehicle {
        return dev.Instance()
    })
}

func main() {
    var vv vehicles
    _ = vv.Instances()
}

Again only a 700 bytes difference.

seankhliao commented 2 hours ago

looks like you have something other than generics that's increasing your binary size

Unlike many projects, the Go project does not use GitHub Issues for general discussion or asking questions. GitHub Issues are used for tracking bugs and proposals only.

For questions please refer to https://github.com/golang/go/wiki/Questions

andig commented 2 hours ago

Appreciate the feedback. If you want to take the time- any idea what could increase binary size when only this single function is being updated? Is there any AST or similar of the compiled program that I could export for inspection? Thank you!