golang / go

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

x/mobile: performance degradation by 2 orders of magnitude when running inside a Swift app #61203

Open arroz opened 1 year ago

arroz commented 1 year ago

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

go version go1.20.5 darwin/amd64

Does this issue reproduce with the latest release?

Yes.

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

go env Output
GO111MODULE=""
GOARCH="amd64"
GOBIN=""
GOCACHE="/Users/user/Library/Caches/go-build"
GOENV="/Users/user/Library/Application Support/go/env"
GOEXE=""
GOEXPERIMENT=""
GOFLAGS=""
GOHOSTARCH="amd64"
GOHOSTOS="darwin"
GOINSECURE=""
GOMODCACHE="/Users/user/go/pkg/mod"
GONOPROXY=""
GONOSUMDB=""
GOOS="darwin"
GOPATH="/Users/user/go"
GOPRIVATE=""
GOPROXY="https://proxy.golang.org,direct"
GOROOT="/usr/local/go"
GOSUMDB="sum.golang.org"
GOTMPDIR=""
GOTOOLDIR="/usr/local/go/pkg/tool/darwin_amd64"
GOVCS=""
GOVERSION="go1.20.5"
GCCGO="gccgo"
GOAMD64="v1"
AR="ar"
CC="clang"
CXX="clang++"
CGO_ENABLED="1"
GOMOD="/dev/null"
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 x86_64 -m64 -pthread -fno-caret-diagnostics -Qunused-arguments -fmessage-length=0 -fdebug-prefix-map=/var/folders/0s/b1y_wjhc8xl6nc008k8dhz1h0000gq/T/go-build2158411908=/tmp/go-build -gno-record-gcc-switches -fno-common"

What did you do?

I'm trying to integrate some Go code with a Swift app. I noticed that code runs much slower when integrated on the Swift app than standalone on a pure Go environment. I did some experiments and came up with an interesting use case. Take this function:

func Something() {
    start := time.Now()
    var numbers []int
    for i := 0; i < 1000000; i++ {
        numbers = append(numbers, i)
    }
    end := time.Now()
    fmt.Printf("Duration: %vms\n", end.UnixMilli()-start.UnixMilli())
}

I wrote a simple benchmark for this:

func BenchmarkSomething(b *testing.B) {
    for i := 0; i < b.N; i++ {
        Something()
    }
}

When running this from Visual Studio or directly from the command line using go test -bench=., this function takes about 7ms to run. However, after building a framework for integrating with the Swift app (using gomobile bind -target macos -ldflags="-s -w" -o hello.xcframework), it takes, at best, ~700ms to run.

If I remove the code that creates and appends to the slice, the execution time is similar on both environments, so I suspect this is something related with memory management.

I wondered if the gomobile command was building without optimizations or with any kind of debug code, but as far as I can understand (new Go user here), there's nothing like -O here (Go builds are always as optimized as they can be?). So I'm a bit stuck on why this is so much slower when running from Swift. I'm ruling out any bridging code since the slowness happens entirely inside Go code, not pushing data back and forth between both worlds.

What did you expect to see?

A similar execution time in both cases.

What did you see instead?

Running from Swift is ~100x slower.

arroz commented 1 year ago

Interesting: if I run the app directly from Finder, or if I launch it from Xcode after turning off the "Debug Executable" option, it runs at the expected speed. So this seems some kind of instrumentation Xcode is adding to the runtime (even if I build in Release mode) that slows down things considerably on the Go side (I don't see this kind of performance degradation on Swift code).

prionator commented 7 months ago

@arroz Indeed, it's possible that what you're seeing here is an effect of XCode's instrumented environment.