0
1
mirror of https://github.com/golang/go synced 2025-06-12 17:11:51 +00:00
Files
go/test/escape_iface_data.go
thepudds c3bb27bbc7 cmd/compile/internal/walk: use global zeroVal in interface conversions for zero values
This is a small-ish adjustment to the change earlier in our
stack in CL 649555, which started creating read-only global storage
for a composite literal used in an interface conversion and setting
the interface data pointer to point to that global storage.

In some cases, there are execution-time performance benefits to point
to runtime.zeroVal in particular. In reflect, pointer checks against
the runtime.zeroVal memory address are used to side-step some work,
such as in reflect.Value.Set and reflect.Value.IsZero.

In this CL, we therefore dig up the zeroVal symbol, and we use the
machinery from earlier in our stack to use a pointer to zeroVal for
the interface data pointer if we see examples like:

    sink = S{}
or:
    s := S{}
    sink = s

CL 649076 (also earlier in our stack) added most of the tests
along with debug diagnostics in convert.go to make it easier
to test this change.

We add a benchmark in reflect to show examples of performance benefit.
The left column is our immediately prior CL 649555, and the right is
this CL. (The arrays of structs here do not seem to benefit, which
we attempt to address in our next CL).

goos: linux
goarch: amd64
pkg: reflect
cpu: Intel(R) Xeon(R) CPU @ 2.80GHz
                                          │  cl-649555   │           new                       │
                                          │    sec/op    │   sec/op     vs base                │
Zero/IsZero/ByteArray/size=16-4              4.176n ± 0%   4.171n ± 0%        ~ (p=0.151 n=20)
Zero/IsZero/ByteArray/size=64-4              6.921n ± 0%   3.864n ± 0%  -44.16% (p=0.000 n=20)
Zero/IsZero/ByteArray/size=1024-4           21.210n ± 0%   3.878n ± 0%  -81.72% (p=0.000 n=20)
Zero/IsZero/BigStruct/size=1024-4           25.505n ± 0%   5.061n ± 0%  -80.15% (p=0.000 n=20)
Zero/IsZero/SmallStruct/size=16-4            4.188n ± 0%   4.191n ± 0%        ~ (p=0.106 n=20)
Zero/IsZero/SmallStructArray/size=64-4       8.639n ± 0%   8.636n ± 0%        ~ (p=0.973 n=20)
Zero/IsZero/SmallStructArray/size=1024-4     79.99n ± 0%   80.06n ± 0%        ~ (p=0.213 n=20)
Zero/IsZero/Time/size=24-4                   7.232n ± 0%   3.865n ± 0%  -46.56% (p=0.000 n=20)
Zero/SetZero/ByteArray/size=16-4             13.47n ± 0%   13.09n ± 0%   -2.78% (p=0.000 n=20)
Zero/SetZero/ByteArray/size=64-4             14.14n ± 0%   13.70n ± 0%   -3.15% (p=0.000 n=20)
Zero/SetZero/ByteArray/size=1024-4           24.22n ± 0%   20.18n ± 0%  -16.68% (p=0.000 n=20)
Zero/SetZero/BigStruct/size=1024-4           24.24n ± 0%   20.18n ± 0%  -16.73% (p=0.000 n=20)
Zero/SetZero/SmallStruct/size=16-4           13.45n ± 0%   13.10n ± 0%   -2.60% (p=0.000 n=20)
Zero/SetZero/SmallStructArray/size=64-4      14.12n ± 0%   13.69n ± 0%   -3.05% (p=0.000 n=20)
Zero/SetZero/SmallStructArray/size=1024-4    24.62n ± 0%   21.61n ± 0%  -12.26% (p=0.000 n=20)
Zero/SetZero/Time/size=24-4                  13.59n ± 0%   13.40n ± 0%   -1.40% (p=0.000 n=20)
geomean                                      14.06n        10.19n       -27.54%

Finally, here are results from the benchmark example from .
Note however that almost all the benefit shown here is from our earlier
CL 649555, which is a more general purpose change and eliminates
the allocation using a different read-only global than this CL.

             │   go1.24       │               new                    │
             │     sec/op     │    sec/op     vs base                │
InterfaceAny   112.6000n ± 5%   0.8078n ± 3%  -99.28% (p=0.000 n=20)
ReflectValue      11.63n ± 2%    11.59n ± 0%        ~ (p=0.330 n=20)

             │  go1.24.out  │                 new.out                 │
             │     B/op     │    B/op     vs base                     │
InterfaceAny   224.0 ± 0%       0.0 ± 0%  -100.00% (p=0.000 n=20)
ReflectValue   0.000 ± 0%     0.000 ± 0%         ~ (p=1.000 n=20) ¹

             │  go1.24.out  │                 new.out                 │
             │  allocs/op   │ allocs/op   vs base                     │
InterfaceAny   1.000 ± 0%     0.000 ± 0%  -100.00% (p=0.000 n=20)
ReflectValue   0.000 ± 0%     0.000 ± 0%         ~ (p=1.000 n=20) ¹

Updates 
Updates 

Change-Id: I64d8cf1a7900f011d2ec59b948388aeda1150676
Reviewed-on: https://go-review.googlesource.com/c/go/+/649078
LUCI-TryBot-Result: Go LUCI <golang-scoped@luci-project-accounts.iam.gserviceaccount.com>
Reviewed-by: Keith Randall <khr@golang.org>
Reviewed-by: Keith Randall <khr@google.com>
Reviewed-by: David Chase <drchase@google.com>
2025-05-21 12:24:22 -07:00

298 lines
4.9 KiB
Go

// errorcheck -0 -d=escapedebug=1
// Copyright 2024 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// Test the data word used for interface conversions
// that might otherwise allocate.
package dataword
var sink interface{}
func string1() {
sink = "abc" // ERROR "using global for interface value"
}
func string2() {
v := "abc"
sink = v // ERROR "using global for interface value"
}
func string3() {
sink = "" // ERROR "using global for interface value"
}
func string4() {
v := ""
sink = v // ERROR "using global for interface value"
}
func string5() {
var a any = "abc" // ERROR "using global for interface value"
_ = a
}
func string6() {
var a any
v := "abc"
a = v // ERROR "using global for interface value"
_ = a
}
// string7 can be inlined.
func string7(v string) {
sink = v
}
func string8() {
v0 := "abc"
v := v0
string7(v) // ERROR "using global for interface value"
}
func string9() {
v0 := "abc"
v := v0
f := func() {
string7(v)
}
f() // ERROR "using global for interface value"
}
func string10() {
v0 := "abc"
v := v0
f := func() {
f2 := func() {
string7(v)
}
f2()
}
f() // ERROR "using global for interface value"
}
func string11() {
v0 := "abc"
v := v0
defer func() {
string7(v) // ERROR "using global for interface value"
}()
}
func integer1() {
sink = 42 // ERROR "using global for interface value"
}
func integer2() {
v := 42
sink = v // ERROR "using global for interface value"
}
func integer3() {
sink = 0 // ERROR "using global for interface value"
}
func integer4a() {
v := 0
sink = v // ERROR "using global for interface value"
}
func integer4b() {
v := uint8(0)
sink = v // ERROR "using global for single-byte interface value"
}
func integer5() {
var a any = 42 // ERROR "using global for interface value"
_ = a
}
func integer6() {
var a any
v := 42
a = v // ERROR "using global for interface value"
_ = a
}
func integer7(v int) {
sink = v
}
type M interface{ M() }
type MyInt int
func (m MyInt) M() {}
func escapes(m M) {
sink = m
}
func named1a() {
sink = MyInt(42) // ERROR "using global for interface value"
}
func named1b() {
escapes(MyInt(42)) // ERROR "using global for interface value"
}
func named2a() {
v := MyInt(0)
sink = v // ERROR "using global for interface value"
}
func named2b() {
v := MyInt(42)
escapes(v) // ERROR "using global for interface value"
}
func named2c() {
v := 42
sink = MyInt(v) // ERROR "using global for interface value"
}
func named2d() {
v := 42
escapes(MyInt(v)) // ERROR "using global for interface value"
}
func named3a() {
sink = MyInt(42) // ERROR "using global for interface value"
}
func named3b() {
escapes(MyInt(0)) // ERROR "using global for interface value"
}
func named4a() {
v := MyInt(0)
sink = v // ERROR "using global for interface value"
}
func named4b() {
v := MyInt(0)
escapes(v) // ERROR "using global for interface value"
}
func named4c() {
v := 0
sink = MyInt(v) // ERROR "using global for interface value"
}
func named4d() {
v := 0
escapes(MyInt(v)) // ERROR "using global for interface value"
}
func named5() {
var a any = MyInt(42) // ERROR "using global for interface value"
_ = a
}
func named6() {
var a any
v := MyInt(42)
a = v // ERROR "using global for interface value"
_ = a
}
func named7a(v MyInt) {
sink = v
}
func named7b(v MyInt) {
escapes(v)
}
type S struct{ a, b int64 }
func struct1() {
sink = S{1, 1} // ERROR "using global for interface value"
}
func struct2() {
v := S{1, 1}
sink = v // ERROR "using global for interface value"
}
func struct3() {
sink = S{} // ERROR "using global for zero value interface value"
}
func struct4() {
v := S{}
sink = v // ERROR "using global for zero value interface value"
}
func struct5() {
var a any = S{1, 1} // ERROR "using global for interface value"
_ = a
}
func struct6() {
var a any
v := S{1, 1}
a = v // ERROR "using global for interface value"
_ = a
}
func struct7(v S) {
sink = v
}
func emptyStruct1() {
sink = struct{}{} // ERROR "using global for zero-sized interface value"
}
func emptyStruct2() {
v := struct{}{}
sink = v // ERROR "using global for zero-sized interface value"
}
func emptyStruct3(v struct{}) { // ERROR "using global for zero-sized interface value"
sink = v
}
// Some light emulation of conditional debug printing (such as in #53465).
func Printf(format string, args ...any) {
for _, arg := range args {
sink = arg
}
}
var enabled = true
func debugf(format string, args ...interface{}) {
if enabled {
Printf(format, args...)
}
}
//go:noinline
func debugf2(format string, args ...interface{}) {
if enabled {
Printf(format, args...)
}
}
func f1() {
v := 1000
debugf("hello %d", v) // ERROR "using global for interface value"
}
func f2() {
v := 1000
debugf2("hello %d", v) // ERROR "using global for interface value"
}
//go:noinline
func f3(i int) {
debugf("hello %d", i)
}
func f4() {
f3(1000)
}