The performance of using interface{} vs a specific type.

I've recently started learning Go, and I have to say I'm very amazed by the whole concept of Goroutines, specially coming from a NodeJS / C++ background.

But sometimes it's nice to have a variable with a dynamic type, which Go does support (aka interface{}) , then I started wondering about the performance of using it vs just using one specific type.

I wrote a simple benchmark, at first I was rather disappointed at how slow it was compared to using the direct type, then I tried using goroutines to speed it up, with both buffered and unbuffered channels, surprisingly it didn't help at all, it even slowed it down farther.

Since the benchmark is simply summing some numbers, I decided to try atomic.AddUint64(), and while it was a tiny bit slower for defined types, it was much faster using interface{}

The Results :


6 comments on “The performance of using interface{} vs a specific type.”

  1. Here's the updated numbers:

    go test main_test.go -bench=.
    goos: linux
    goarch: amd64
    cpu: AMD Ryzen 7 5825U with Radeon Graphics
    BenchmarkInterface-16 32020794 33.78 ns/op
    BenchmarkType-16 54066788 20.19 ns/op
    BenchmarkInterfaceGoroutines-16 1606332 755.4 ns/op
    BenchmarkTypeGoroutines-16 1606047 745.6 ns/op
    BenchmarkInterfaceGoroutinesBuffered-16 3234037 373.2 ns/op
    BenchmarkTypeGoroutinesBuffered-16 3226484 376.9 ns/op
    BenchmarkInterfaceGoroutinesAtomic-16 18934428 67.38 ns/op
    BenchmarkTypeGoroutinesAtomic-16 21858744 53.02 ns/op

  2. At this point, I'd take your results thus far with a rather large grain of salt.

    First, to expect a speedup from using goroutines, you need to tell the runtime how many CPU cores you want to let it use. Otherwise, it will simply run the goroutines in a single thread - good for concurrency, but offers no parallelism, and so will be slower.

    Second, you are probably not really testing interface vs type, but casting (twice) vs no casting. You cast your arguments implicitly into uint64 in sumI, then cast the results again in your test harness. To get the test a little closer to parity you should probably not use the empty interface, and/or should return a concrete type rather than an interface. Instead of the empty interface define your own "addable" interface with a method that just uses "+", then use that method in your sumI function.

    Happy gophering!

    1. You make good points, however :

      1. GOMAXPROCS is exported in ~/.bashrc, hence why all the benchmarks have -4.
      2. It's not a cast per se, it's a conversation, for example if you try v.(float64), it will give you an error.

      1. Is there really a difference between (1) type cast (2) type conversion ?
        From the top of my head, casting usually refers to navigation of inheritance relationships.

    2. Whether running single-thread or multi-thread concurrency among goroutines is still an issue, and you will have to code accordingly.

Leave a Reply

Your email address will not be published. Required fields are marked *

You may use these HTMLtags and attributes:
<a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code class="" title="" data-url=""> <del datetime=""> <em> <i> <q cite=""> <s> <strike> <strong> <pre class="" title="" data-url=""> <span class="" title="" data-url="">