Go: The price of interface{}
Summary
Go’s empty interface{}
is the interface that everything implements. It allows functions that can be passed any type. The function func f(any interface{})
can be called with a string f("a string")
, an integer f(42)
, a custom type, or anything else.
This flexibility comes at a cost. When you assign a value to a type interface{}
, Go will call runtime.convT2E to create the interface structure (read more about Go interface internals). That requires a memory allocation. More memory allocations means more garbage on the heap, which means longer garbage collection pauses.
Here’s an example. A log function two ways, neither of which do anything because we’re logging at ‘prod’ level, and the messages are ‘debug’. One is more expensive than the other.
package main
import "fmt"
const (
debug byte = iota
prod
)
var logLevel = prod
func main() {
logIface(debug, "Test interface")
logString(debug, "Test string")
}
func logIface(level byte, msg interface{}) {
if level >= logLevel {
fmt.Println(msg)
}
}
func logString(level byte, msg string) {
if level >= logLevel {
fmt.Println(msg)
}
}
And here are benchmarks:
package main
import "testing"
func BenchmarkIface(b *testing.B) {
for i := 0; i < b.N; i++ {
logIface(debug, "test iface")
}
}
func BenchmarkString(b *testing.B) {
for i := 0; i < b.N; i++ {
logString(debug, "test string")
}
}
Here’s the output of go test -bench Benchmark -benchmem
on my machine, go1.4.2 linux/amd64:
BenchmarkIface 10000000 126 ns/op 16 B/op 1 allocs/op
BenchmarkString 200000000 6.11 ns/op 0 B/op 0 allocs/op
To see what is happening ask Go for the assembly output:
go build -gcflags -S <filename>.go
Notice the CALL runtime.convT2E
before CALL logIface
, but not before CALL logString
.
Or use the fantastic Go Compiler Exporer. The call to runtime.convT2E
is on line 27.
Even though logIface
doesn’t do anything, the runtime still needs to convert the string to an interface to call the function, and that’s the memory allocation. It’s good to look out for interface{}
usage in your inner-most loops - look for fmt
and log
package functions, container types, and many other places.
interface{}
is a wonderful thing to have, except in the most frequently called sections of you code.