@randall77 was mentioning the compiler maybe inlining reflect.TypeOf calls the other day. Or somebody was.
Today I ran across the github.com/vmware/govmomi/vim25/types package which has 5769 calls to reflect.TypeOf, populating a map:
types.go: t["WillLoseHAProtection"] = reflect.TypeOf((*WillLoseHAProtection)(nil)).Elem()
types.go: t["WillLoseHAProtectionFault"] = reflect.TypeOf((*WillLoseHAProtectionFault)(nil)).Elem()
types.go: t["WillModifyConfigCpuRequirements"] = reflect.TypeOf((*WillModifyConfigCpuRequirements)(nil)).Elem()
types.go: t["WillModifyConfigCpuRequirementsFault"] = reflect.TypeOf((*WillModifyConfigCpuRequirementsFault)(nil)).Elem()
types.go: t["WillResetSnapshotDirectory"] = reflect.TypeOf((*WillResetSnapshotDirectory)(nil)).Elem()
types.go: t["WillResetSnapshotDirectoryFault"] = reflect.TypeOf((*WillResetSnapshotDirectoryFault)(nil)).Elem()
types.go: t["WinNetBIOSConfigInfo"] = reflect.TypeOf((*WinNetBIOSConfigInfo)(nil)).Elem()
types.go: t["WipeDiskFault"] = reflect.TypeOf((*WipeDiskFault)(nil)).Elem()
types.go: t["WipeDiskFaultFault"] = reflect.TypeOf((*WipeDiskFaultFault)(nil)).Elem()
types.go: t["XmlToCustomizationSpecItem"] = reflect.TypeOf((*XmlToCustomizationSpecItem)(nil)).Elem()
types.go: t["XmlToCustomizationSpecItemRequestType"] = reflect.TypeOf((*XmlToCustomizationSpecItemRequestType)(nil)).Elem()
types.go: t["ZeroFillVirtualDiskRequestType"] = reflect.TypeOf((*ZeroFillVirtualDiskRequestType)(nil)).Elem()
types.go: t["ZeroFillVirtualDisk_Task"] = reflect.TypeOf((*ZeroFillVirtualDisk_Task)(nil)).Elem()
If the compiler could inline the reflect.TypeOf
calls then they could stop autogenerating the Elem part too, and it's all just static data and their init load could drop a bunch.
reflect.TypeOf is just:
// TypeOf returns the reflection Type that represents the dynamic type of i.
// If i is a nil interface value, TypeOf returns nil.
func TypeOf(i interface{}) Type {
eface := *(*emptyInterface)(unsafe.Pointer(&i))
return toType(eface.typ)
}
func toType(t *rtype) Type {
if t == nil {
return nil
}
return t
}
Low priority.
Comment From: bradfitz
/cc @josharian
Comment From: robpike
That's a lot of reflect.TypeOfs.
Comment From: bradfitz
@robpike, this is generated code from something akin to our protocol buffers or google-api-go-client. So it's the sort of auto-generated gobbledygook you might expect.
Comment From: JayNakrani
Compiler seems to be inlining reflect.TypeOf()
already.
I tried with,
$ go build -gcflags="-m" github.com/vmware/govmomi/vim25/types 2>&1 | grep "types\.go.*inlining call to reflect\."
src/github.com/vmware/govmomi/vim25/types/types.go:28: inlining call to reflect.TypeOf
src/github.com/vmware/govmomi/vim25/types/types.go:28: inlining call to reflect.toType
src/github.com/vmware/govmomi/vim25/types/types.go:37: inlining call to reflect.TypeOf
src/github.com/vmware/govmomi/vim25/types/types.go:37: inlining call to reflect.toType
...
Comment From: egorse
@bradfitz does that means that something similar to
type A struct {
V1 int
}
type B struct {
V1 string
V2 int
}
var table = []reflect.Type{
reflect.TypeOf(A{}),
reflect.TypeOf(B{}),
}
should lead to no calls to reflect.TypeOf at all ?
Comment From: bradfitz
@egorse, correct. No calls to reflect.TypeOf. Currently with your example code I see:
"".init STEXT size=317 args=0x0 locals=0x48
0x0000 00000 (<autogenerated>:1) TEXT "".init(SB), $72-0
0x0000 00000 (<autogenerated>:1) MOVQ (TLS), CX
0x0009 00009 (<autogenerated>:1) CMPQ SP, 16(CX)
0x000d 00013 (<autogenerated>:1) JLS 307
0x0013 00019 (<autogenerated>:1) SUBQ $72, SP
0x0017 00023 (<autogenerated>:1) MOVQ BP, 64(SP)
0x001c 00028 (<autogenerated>:1) LEAQ 64(SP), BP
0x0021 00033 (<autogenerated>:1) FUNCDATA $0, gclocals·69c1753bd5f81501d95132d08af04464(SB)
0x0021 00033 (<autogenerated>:1) FUNCDATA $1, gclocals·713abd6cdf5e052e4dcd3eb297c82601(SB)
0x0021 00033 (<autogenerated>:1) MOVBLZX "".initdone·(SB), AX
0x0028 00040 (<autogenerated>:1) CMPB AL, $1
0x002a 00042 (<autogenerated>:1) JLS 54
0x002c 00044 (<autogenerated>:1) MOVQ 64(SP), BP
0x0031 00049 (<autogenerated>:1) ADDQ $72, SP
0x0035 00053 (<autogenerated>:1) RET
0x0036 00054 (<autogenerated>:1) JNE 63
0x0038 00056 (<autogenerated>:1) PCDATA $0, $0
0x0038 00056 (<autogenerated>:1) CALL runtime.throwinit(SB)
0x003d 00061 (<autogenerated>:1) UNDEF
0x003f 00063 (<autogenerated>:1) MOVB $1, "".initdone·(SB)
0x0046 00070 (<autogenerated>:1) PCDATA $0, $0
0x0046 00070 (<autogenerated>:1) CALL reflect.init(SB)
0x004b 00075 (x.go:15) MOVQ $0, ""..autotmp_0+32(SP)
0x0054 00084 (x.go:15) LEAQ type."".A(SB), AX
0x005b 00091 (x.go:15) MOVQ AX, (SP)
0x005f 00095 (x.go:15) LEAQ ""..autotmp_0+32(SP), AX
0x0064 00100 (x.go:15) MOVQ AX, 8(SP)
0x0069 00105 (x.go:15) PCDATA $0, $0
0x0069 00105 (x.go:15) CALL runtime.convT2E64(SB)
0x006e 00110 (x.go:15) MOVUPS 16(SP), X0
0x0073 00115 (x.go:15) MOVUPS X0, (SP)
0x0077 00119 (x.go:15) PCDATA $0, $0
0x0077 00119 (x.go:15) CALL reflect.TypeOf(SB)
0x007c 00124 (x.go:15) MOVQ 16(SP), AX
0x0081 00129 (x.go:15) MOVQ 24(SP), CX
0x0086 00134 (x.go:15) MOVQ AX, "".statictmp_0(SB)
0x008d 00141 (x.go:15) MOVL runtime.writeBarrier(SB), AX
0x0093 00147 (x.go:15) TESTL AX, AX
0x0095 00149 (x.go:15) JNE 287
0x009b 00155 (x.go:15) MOVQ CX, "".statictmp_0+8(SB)
0x00a2 00162 (x.go:16) XORPS X0, X0
0x00a5 00165 (x.go:16) MOVUPS X0, ""..autotmp_1+40(SP)
0x00aa 00170 (x.go:16) MOVQ $0, ""..autotmp_1+56(SP)
0x00b3 00179 (x.go:16) LEAQ type."".B(SB), AX
0x00ba 00186 (x.go:16) MOVQ AX, (SP)
0x00be 00190 (x.go:16) LEAQ ""..autotmp_1+40(SP), AX
0x00c3 00195 (x.go:16) MOVQ AX, 8(SP)
0x00c8 00200 (x.go:16) PCDATA $0, $1
0x00c8 00200 (x.go:16) CALL runtime.convT2E(SB)
0x00cd 00205 (x.go:16) MOVUPS 16(SP), X0
0x00d2 00210 (x.go:16) MOVUPS X0, (SP)
0x00d6 00214 (x.go:16) PCDATA $0, $1
0x00d6 00214 (x.go:16) CALL reflect.TypeOf(SB)
0x00db 00219 (x.go:16) MOVQ 16(SP), AX
0x00e0 00224 (x.go:16) MOVQ 24(SP), CX
0x00e5 00229 (x.go:16) MOVQ AX, "".statictmp_0+16(SB)
0x00ec 00236 (x.go:16) MOVL runtime.writeBarrier(SB), AX
0x00f2 00242 (x.go:16) TESTL AX, AX
0x00f4 00244 (x.go:16) JNE 270
0x00f6 00246 (x.go:16) MOVQ CX, "".statictmp_0+24(SB)
0x00fd 00253 (<autogenerated>:1) MOVB $2, "".initdone·(SB)
0x0104 00260 (<autogenerated>:1) MOVQ 64(SP), BP
0x0109 00265 (<autogenerated>:1) ADDQ $72, SP
0x010d 00269 (<autogenerated>:1) RET
0x010e 00270 (x.go:16) LEAQ "".statictmp_0+24(SB), DI
0x0115 00277 (x.go:16) MOVQ CX, AX
0x0118 00280 (x.go:16) CALL runtime.gcWriteBarrier(SB)
0x011d 00285 (x.go:16) JMP 253
0x011f 00287 (x.go:15) LEAQ "".statictmp_0+8(SB), DI
0x0126 00294 (x.go:15) MOVQ CX, AX
0x0129 00297 (x.go:15) CALL runtime.gcWriteBarrier(SB)
0x012e 00302 (x.go:15) JMP 162
0x0133 00307 (x.go:15) NOP
Comment From: josharian
Note that there are actually two separate things here: treating reflect.TypeOf as an intrinsic, and handling reflect.TypeOf when generating static data. I think we should do both, and I just encountered some hot code in which the former would help. Note that treating reflect.TypeOf as an intrinsic is better than inlining it, because we can avoid creating an interface value, which often allocates. (Purity analysis might also help here, but reflect.TypeOf is a great intrinsic candidate anyway.)
Comment From: bradfitz
@randall77, I ran into this again. Know anybody on the compiler team who is looking for a project? :)
Comment From: randall77
So reflect.TypeOf
does inline. Try this program:
package p
import "reflect"
type A struct {
V1 int
}
type B struct {
V1 string
V2 int
}
var Table = []reflect.Type{
reflect.TypeOf(A{}),
reflect.TypeOf(B{}),
}
func f() {
Table = []reflect.Type{
reflect.TypeOf(A{}),
reflect.TypeOf(B{}),
}
}
reflect.TypeOf
does inline in f
, but it does not inline in the generated init function. It looks like we're not calling the inliner in generated init functions. This patch fixes it:
--- a/src/cmd/compile/internal/gc/init.go
+++ b/src/cmd/compile/internal/gc/init.go
@@ -60,6 +60,7 @@ func fninit(n []*Node) {
Curfn = fn
typecheckslice(nf, ctxStmt)
Curfn = nil
+ inlcalls(fn)
funccompile(fn)
fns = append(fns, initializers.Linksym())
}
The inlined code isn't great either, in that it doesn't realize that it doesn't need the data word of the interface, just type word, so it is still calling one of the runtime.convT2E
functions. The result ends up getting thrown away.
Comment From: gopherbot
Change https://go.dev/cl/700136 mentions this issue: cmd/compile/internal/ssa: handle reflect.TypeFor in SSA