Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

runtime: redundant allocation after type assertion #64541

Open
AlexanderYastrebov opened this issue Dec 4, 2023 · 2 comments
Open

runtime: redundant allocation after type assertion #64541

AlexanderYastrebov opened this issue Dec 4, 2023 · 2 comments
Labels
compiler/runtime Issues related to the Go compiler and/or runtime. NeedsInvestigation Someone must examine and confirm this is a valid issue and not a duplicate of an existing one. Performance
Milestone

Comments

@AlexanderYastrebov
Copy link
Contributor

Go version

go version go1.21.4 linux/amd64

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

GO111MODULE=''
GOARCH='amd64'
GOBIN=''
GOEXE=''
GOEXPERIMENT=''
GOFLAGS=''
GOHOSTARCH='amd64'
GOHOSTOS='linux'
GOINSECURE=''
GONOPROXY=''
GONOSUMDB=''
GOOS='linux'
GOPRIVATE=''
GOPROXY='https://proxy.golang.org,direct'
GOSUMDB='sum.golang.org'
GOTMPDIR=''
GOTOOLCHAIN='auto'
GOVCS=''
GOVERSION='go1.21.4'
GCCGO='gccgo'
GOAMD64='v1'
AR='ar'
CC='gcc'
CXX='g++'
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'

What did you do?

Consider example snippet and a benchmark:

package main

import (
	"fmt"
	"testing"
)

var gv any

func sink(v any) {
	gv = v
}

func setString(v any) {
	if s, ok := v.(string); ok {
		sink(s)
	} else {
		sink(fmt.Sprint(v))
		panic("unreachable")
	}
}

func setString2(v any) {
	if _, ok := v.(string); ok {
		sink(v)
	} else {
		sink(fmt.Sprint(v))
		panic("unreachable")
	}
}

func BenchmarkInterfaceToString(b *testing.B) {
	var v any
	v = "hello"

	b.Run("setString", func(b *testing.B) {
		for i := 0; i < b.N; i++ {
			setString(v)
		}
	})

	b.Run("setString2", func(b *testing.B) {
		for i := 0; i < b.N; i++ {
			setString2(v)
		}
	})
}

What did you expect to see?

No difference in benchmarks for setString and setString2.

What did you see instead?

setString allocates to convert string s into any argument of sink although s is obtained from any v.

$ go test ./stringany_test.go -run=NONE -bench=Benchmark -benchmem
goos: linux
goarch: amd64
cpu: Intel(R) Core(TM) i5-8350U CPU @ 1.70GHz
BenchmarkInterfaceToString/setString-8          16101618                67.47 ns/op           16 B/op          1 allocs/op
BenchmarkInterfaceToString/setString2-8         194076378                5.335 ns/op           0 B/op          0 allocs/op
PASS
ok      command-line-arguments  2.828s
$ go test ./stringany_test.go -run=NONE -bench='BenchmarkInterfaceToString/setString$' -count=10 -benchmem -cpuprofile=/tmp/cpu.pprof
$ go tool pprof /tmp/cpu.pprof 
File: main.test
Type: cpu
Time: Dec 4, 2023 at 10:54pm (CET)
Duration: 13.85s, Total samples = 14.37s (103.76%)
Entering interactive mode (type "help" for commands, "o" for options)
(pprof) top
Showing nodes accounting for 12120ms, 84.34% of 14370ms total
Dropped 113 nodes (cum <= 71.85ms)
Showing top 10 nodes out of 38
      flat  flat%   sum%        cum   cum%
    4470ms 31.11% 31.11%    10610ms 73.83%  runtime.mallocgc
    1380ms  9.60% 40.71%     1430ms  9.95%  runtime.writeHeapBits.flush
    1310ms  9.12% 49.83%     1310ms  9.12%  runtime.nextFreeFast (inline)
    1060ms  7.38% 57.20%    11670ms 81.21%  runtime.convTstring
    1050ms  7.31% 64.51%    13860ms 96.45%  command-line-arguments.BenchmarkInterfaceToString.func1
     930ms  6.47% 70.98%     2780ms 19.35%  runtime.heapBitsSetType
     690ms  4.80% 75.78%    12810ms 89.14%  command-line-arguments.setString
     440ms  3.06% 78.84%      450ms  3.13%  command-line-arguments.sink (inline)
     400ms  2.78% 81.63%      400ms  2.78%  runtime.acquirem (inline)
     390ms  2.71% 84.34%      440ms  3.06%  runtime.deductAssistCredit

Perhaps compiler could figure out that s is v and pass v to sink instead.

@gopherbot gopherbot added the compiler/runtime Issues related to the Go compiler and/or runtime. label Dec 4, 2023
@zigo101
Copy link

zigo101 commented Dec 5, 2023

The compiler is still not as smart as you think.
Another example (from my book "Go Optimizations 101"):

package main

import (
	"fmt"
	"io"
	"testing"
)

func main() {
	stat := func(f func()) int {
		allocs := testing.AllocsPerRun(100, f)
		return int(allocs)
	}
	
	var x = "aaa"
	
	var n = stat(func(){
		// 3 allocations
		fmt.Fprint(io.Discard, x, x, x)
	})
	println(n) // 3
	
	var m = stat(func(){
		var i interface{} = x // 1 allocation
		// No allocations
		fmt.Fprint(io.Discard, i, i, i)
	})
	println(m) // 1
}

It is type assertion weak related.

@mknyszek
Copy link
Contributor

mknyszek commented Dec 6, 2023

CC @golang/compiler

@mknyszek mknyszek added this to the Unplanned milestone Dec 6, 2023
@mknyszek mknyszek added Performance NeedsInvestigation Someone must examine and confirm this is a valid issue and not a duplicate of an existing one. labels Dec 6, 2023
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
compiler/runtime Issues related to the Go compiler and/or runtime. NeedsInvestigation Someone must examine and confirm this is a valid issue and not a duplicate of an existing one. Performance
Projects
Development

No branches or pull requests

4 participants