package main import ( "math/rand" "runtime" "testing" ) // ----------------------------------------------------------------------------- type identifier interface { idInline() int32 idNoInline() int32 } type id32 struct{ id int32 } // NOTE: Use pointer receivers so we don't measure the extra overhead incurred by // autogenerated wrappers as part of our results. func (id *id32) idInline() int32 { return id.id } //go:noinline func (id *id32) idNoInline() int32 { return id.id } // ----------------------------------------------------------------------------- const _maxSize = 2097152 // 2^21 const _maxSizeModMask = _maxSize - 1 // avoids a mod (%) in the hot path var _randIndexes = [_maxSize]int{} func init() { rand.Seed(42) for i := range _randIndexes { _randIndexes[i] = rand.Intn(_maxSize) } } var escapeMePlease *id32 // escapeToHeap makes sure that `id` escapes to the heap. // // In simple situations such as some of the benchmarks present in this file, // the compiler is able to statically infer the underlying type of the // interface (or rather the type of the data it points to, to be pedantic) and // ends up replacing what should have been a dynamic method call by a // static call. // This anti-optimization prevents this extra cleverness. // //go:noinline func escapeToHeap(id *id32) identifier { escapeMePlease = id return escapeMePlease } func BenchmarkMethodCall_direct(b *testing.B) { adders := make([]*id32, _maxSize) for i := range adders { adders[i] = escapeToHeap(&id32{id: int32(i)}).(*id32) } runtime.GC() var myID int32 b.Run("single/noinline", func(b *testing.B) { m := escapeToHeap(&id32{id: 6754}).(*id32) b.ResetTimer() for i := 0; i < b.N; i++ { // CALL "".(*id32).idNoInline(SB) // MOVL 8(SP), AX // MOVQ "".&myID+40(SP), CX // MOVL AX, (CX) myID = m.idNoInline() } }) b.Run("single/inline", func(b *testing.B) { m := escapeToHeap(&id32{id: 6754}).(*id32) b.ResetTimer() for i := 0; i < b.N; i++ { // MOVL (DX), SI // MOVL SI, (CX) myID = m.idInline() } }) b.Run("many/noinline/small_incr", func(b *testing.B) { var m *id32 b.Run("baseline", func(b *testing.B) { for i := 0; i < b.N; i++ { m = adders[i&_maxSizeModMask] } }) b.Run("call", func(b *testing.B) { for i := 0; i < b.N; i++ { m = adders[i&_maxSizeModMask] myID = m.idNoInline() } }) }) b.Run("many/noinline/big_incr", func(b *testing.B) { var m *id32 b.Run("baseline", func(b *testing.B) { j := 0 for i := 0; i < b.N; i++ { m = adders[j&_maxSizeModMask] j += 32 } }) b.Run("call", func(b *testing.B) { j := 0 for i := 0; i < b.N; i++ { m = adders[j&_maxSizeModMask] myID = m.idNoInline() j += 32 } }) }) b.Run("many/noinline/random_incr", func(b *testing.B) { var m *id32 b.Run("baseline", func(b *testing.B) { for i := 0; i < b.N; i++ { m = adders[_randIndexes[i&_maxSizeModMask]] } }) b.Run("call", func(b *testing.B) { for i := 0; i < b.N; i++ { m = adders[_randIndexes[i&_maxSizeModMask]] myID = m.idNoInline() } }) }) } func BenchmarkMethodCall_interface(b *testing.B) { adders := make([]identifier, _maxSize) for i := range adders { adders[i] = escapeToHeap(&id32{id: int32(i)}) } runtime.GC() var myID int32 b.Run("single/noinline", func(b *testing.B) { m := escapeToHeap(&id32{id: 6754}) b.ResetTimer() for i := 0; i < b.N; i++ { // MOVQ 32(AX), CX // MOVQ "".m.data+40(SP), DX // MOVQ DX, (SP) // CALL CX // MOVL 8(SP), AX // MOVQ "".&myID+48(SP), CX // MOVL AX, (CX) myID = m.idNoInline() } }) b.Run("single/inline", func(b *testing.B) { m := escapeToHeap(&id32{id: 6754}) b.ResetTimer() for i := 0; i < b.N; i++ { // MOVQ 24(AX), CX // MOVQ "".m.data+40(SP), DX // MOVQ DX, (SP) // CALL CX // MOVL 8(SP), AX // MOVQ "".&myID+48(SP), CX // MOVL AX, (CX) myID = m.idInline() } }) b.Run("many/noinline/small_incr", func(b *testing.B) { var m identifier b.Run("baseline", func(b *testing.B) { for i := 0; i < b.N; i++ { m = adders[i&_maxSizeModMask] } }) b.Run("call", func(b *testing.B) { for i := 0; i < b.N; i++ { m = adders[i&_maxSizeModMask] myID = m.idNoInline() } }) }) b.Run("many/noinline/big_incr", func(b *testing.B) { var m identifier b.Run("baseline", func(b *testing.B) { j := 0 for i := 0; i < b.N; i++ { m = adders[j&_maxSizeModMask] j += 32 } }) b.Run("call", func(b *testing.B) { j := 0 for i := 0; i < b.N; i++ { m = adders[j&_maxSizeModMask] myID = m.idNoInline() j += 32 } }) }) b.Run("many/noinline/random_incr", func(b *testing.B) { var m identifier b.Run("baseline", func(b *testing.B) { for i := 0; i < b.N; i++ { m = adders[_randIndexes[i&_maxSizeModMask]] } }) b.Run("call", func(b *testing.B) { for i := 0; i < b.N; i++ { m = adders[_randIndexes[i&_maxSizeModMask]] myID = m.idNoInline() } }) }) } func main() {}