Skip to content

runtime: redundant allocation after type assertion #64541

@AlexanderYastrebov

Description

@AlexanderYastrebov

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.

Metadata

Metadata

Assignees

No one assigned

    Labels

    NeedsInvestigationSomeone must examine and confirm this is a valid issue and not a duplicate of an existing one.Performancecompiler/runtimeIssues related to the Go compiler and/or runtime.

    Type

    No type

    Projects

    Status

    Todo

    Milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions