Skip to content

Instantly share code, notes, and snippets.

@x1unix
Last active December 5, 2025 04:26
Show Gist options
  • Select an option

  • Save x1unix/b710aa92735aed446ee76c7e0d6c56ca to your computer and use it in GitHub Desktop.

Select an option

Save x1unix/b710aa92735aed446ee76c7e0d6c56ca to your computer and use it in GitHub Desktop.
Go - struct vs interface cast benchmark

Struct vs Interface Cast Benchmark

This benchmark measures performance of various alternatives to interface cast, which is expensive.
The reason is that interface cast involves "duct type check", and cast to type just checks a type hash value inside iface.

Cases:

  • BenchmarkAssertStruct - Cast any to a concrete struct type (fastest).
  • BenchmarkAssertIface - Cast to an interface.
  • BenchmarkAssertWrapper - Cast to a concrete type and call a field that is an interface.
  • BenchmarkAssertClosure - Cast to a concrete type and call a closure func field.

Results

WASM

goos: js
goarch: wasm
pkg: main
BenchmarkAssertIface
BenchmarkAssertIface   	28571341	        36.54 ns/op
BenchmarkAssertStruct
BenchmarkAssertStruct  	85713501	        12.58 ns/op
BenchmarkAssertWrapper
BenchmarkAssertWrapper 	49999999	        21.42 ns/op
BenchmarkAssertClosure
BenchmarkAssertClosure 	57143030	        21.12 ns/op
PASS

See playground here

Real CPU - AMD

goos: linux
goarch: amd64
pkg: main
cpu: AMD Ryzen 5 7600X 6-Core Processor             
BenchmarkAssertIface-12      	1000000000	        5.597 ns/op
BenchmarkAssertStruct-12     	1000000000	        3.412 ns/op
BenchmarkAssertWrapper-12    	1000000000	        5.702 ns/op
BenchmarkAssertClosure-12    	1000000000	        5.668 ns/op
PASS

vCPU on VPS

goos: linux
goarch: amd64
pkg: main
cpu: AMD EPYC-Rome Processor
BenchmarkAssertIface-2     	1000000000	       12.68 ns/op
BenchmarkAssertStruct-2    	1000000000	        6.342 ns/op
BenchmarkAssertWrapper-2   	1000000000	       12.55 ns/op
BenchmarkAssertClosure-2   	1000000000	       10.61 ns/op
PASS

Conclusion

Concrete type cast is the fastest path. Any attempts to "optimize" strict interface cast by introducing wrappers (BenchmarkAssertClosure and BenchmarkAssertWrapper) did not introduce any impactuful perf improvements (except WASM).

package main_test
import "testing"
var (
tmpVal any
stubConsumerFunc func(val any, ok bool)
)
func initConsumerFunc() {
stubConsumerFunc = func(val any, ok bool) {
if !ok {
tmpVal = nil
return
}
tmpVal = val
}
}
type ProxyGetter interface {
GetProperty(key string) (any, bool)
}
type ProxyValue struct {
val func()
}
func (pv ProxyValue) GetProperty(key string) (any, bool) {
switch key {
case "foo":
return "bar", true
default:
return nil, false
}
}
func tryIface(pv any) {
p := pv.(ProxyGetter)
stubConsumerFunc(p.GetProperty("foo"))
stubConsumerFunc(p.GetProperty("bar"))
}
func BenchmarkAssertIface(b *testing.B) {
initConsumerFunc()
var proxy any = &ProxyValue{}
for n := 0; n < b.N; n++ {
tryIface(proxy)
}
}
func tryStruct(pv any) {
p := pv.(*ProxyValue)
stubConsumerFunc(p.GetProperty("foo"))
stubConsumerFunc(p.GetProperty("bar"))
}
func BenchmarkAssertStruct(b *testing.B) {
initConsumerFunc()
proxy := &ProxyValue{}
for n := 0; n < b.N; n++ {
tryStruct(proxy)
}
}
type ProxyType struct {
p ProxyGetter
}
func (pt ProxyType) GetProperty(key string) (any, bool) {
return pt.p.GetProperty(key)
}
func tryWrapper(pv any) {
p := pv.(*ProxyType)
stubConsumerFunc(p.GetProperty("foo"))
stubConsumerFunc(p.GetProperty("bar"))
}
func BenchmarkAssertWrapper(b *testing.B) {
initConsumerFunc()
proxy := &ProxyType{
p: &ProxyValue{},
}
for n := 0; n < b.N; n++ {
tryWrapper(proxy)
}
}
type ProxyClosure struct {
getter func(string) (any, bool)
}
func (pt ProxyClosure) GetProperty(key string) (any, bool) {
return pt.getter(key)
}
func tryClosure(pv any) {
p := pv.(*ProxyClosure)
stubConsumerFunc(p.GetProperty("foo"))
stubConsumerFunc(p.GetProperty("bar"))
}
func BenchmarkAssertClosure(b *testing.B) {
initConsumerFunc()
orig := new(ProxyValue)
proxy := &ProxyClosure{
getter: func(k string) (any, bool) {
return orig.GetProperty(k)
},
}
for n := 0; n < b.N; n++ {
tryClosure(proxy)
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment