May 27, 2015

Go: The price of interface{}

Posted in Software at 00:43 by graham

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.

3 Comments »

  1. Ralph Corderoy said,

    May 29, 2015 at 16:28

    Nick, see https://en.wikipedia.org/wiki/Escape_analysis

  2. Nick said,

    May 28, 2015 at 21:23

    Roger. Can you please go into more detail about what you mean by “prove the value doesn’t escape.”

    I plan on using interfaces for some important code, so I’d like to make sure that I don’t inadvertently cause extra issues.

  3. Roger Peppe said,

    May 27, 2015 at 17:02

    Assigning to an interface only requires a memory allocation if you’re not assigning a pointer. Also, it may not require an allocation if the compiler can prove the value doesn’t escape.

Leave a Comment

Note: Your comment will only appear on the site once I approve it manually. This can take a day or two. Thanks for taking the time to comment.