Skip to content

Latest commit

 

History

History

05-lessons-learned

Folders and files

NameName
Last commit message
Last commit date

parent directory

..
 
 

Lessons learned

Wow, what a ride! What I meant to be a quick detour to help me better understand what performance improvements generics might bring compared to storing values in interfaces turned into quite a deep-dive. What did we learn along the way?

  • While storing a value in an interface is conceptually similar to "boxing," this term has a specific meaning for object oriented languages and may confuse people.
  • A Go interface is really just a pair of uintptr values that point to the interface's underlying type and value.
  • Escape analysis occurs before optimizations for storing values in an interface.
  • Just because a value is marked as "escapes to heap" does not mean memory is allocated on the heap.

And remember, if you ever want to see if an assignment allocates memory on the heap, all you have to do is take a quick trip to the Go playground:

package main

import (
	"flag"
	"testing"
)

func TestStoringInterfaceValues(t *testing.T) {
	flag.Lookup("test.benchmem").Value.Set("true")
	flag.Lookup("test.benchtime").Value.Set("100x")

	testCases := []struct {
		name     string
		fvnc     func(*testing.B)
		expAlloc int64
		expBytes int64
	}{
		{
			name: "0 malloc, 0 bytes",
			fvnc: func(b *testing.B) {
				var sink interface{}
				b.ResetTimer()
				for i := 0; i < b.N; i++ {
					var x int32
					x = 255
					sink = x
				}
				_ = sink
			},
		},
		{
			name: "1 malloc, 4 bytes",
			fvnc: func(b *testing.B) {
				var sink interface{}
				b.ResetTimer()
				for i := 0; i < b.N; i++ {
					var x int32
					x = 256
					sink = x
				}
				_ = sink
			},
			expAlloc: 1,
			expBytes: 4,
		},
	}

	for i := 0; i < len(testCases); i++ {
		tc := testCases[i]
		t.Run(tc.name, func(t *testing.T) {
			r := testing.Benchmark(tc.fvnc)
			if ea, aa := tc.expAlloc, r.AllocsPerOp(); ea != aa {
				t.Errorf("expAlloc=%d, actAlloc=%d", ea, aa)
			}
			if eb, ab := tc.expBytes, r.AllocedBytesPerOp(); eb != ab {
				t.Errorf("expBytes=%d, actBytes=%d", eb, ab)
			}
		})
	}
}
=== RUN   TestStoringInterfaceValues
=== RUN   TestStoringInterfaceValues/0_malloc,_0_bytes
=== RUN   TestStoringInterfaceValues/1_malloc,_4_bytes
--- PASS: TestStoringInterfaceValues (0.00s)
    --- PASS: TestStoringInterfaceValues/0_malloc,_0_bytes (0.00s)
    --- PASS: TestStoringInterfaceValues/1_malloc,_4_bytes (0.00s)
PASS

Thanks for reading!