It turns out a lot of the time the interface benchmark was taking was in GC related to the particular way interfaces are being used here, not cost that's inherent to all interface calls.
As background--since Go 1.4, when you wrap a non-pointer type like a uint64 in an interface, the interface value holds a pointer, and the actual int is allocated elsewhere. You can see the allocations by using MemStats with the original code: https://play.golang.org/p/UUlVZp7x-r.
If that cost were present every time someone makes an interface call (or most of the time) you'd want to count it, of course, but it often isn't.
You don't get an extra allocation when you store a pointer type in an interface var, which is probably the most common case, like when you put a *bufio.Writer or *os.File in an io.Writer.
You also get only one extra allocation if you store a non-pointer type in an interface var once, then make lots of calls on it, like when you wrap a large slice in a sort.Interface and call sort.Sort on it.
After this benchmark got posted on Twitter and I noticed it was allocating for each iface call, I rigged up a variation where you call Add on an *Int, so you don't get an alloc per call: https://play.golang.org/p/OgAcagFmZi
The results I got were 0 bytes allocated and 177.376219ms for the concrete calls, 16 bytes allocated and 210.502363ms for the indirect calls. I'm guessing the switch to using pointers was what made the concrete calls slower. Compiling with -gcflags -m shows the concrete call to Add still gets inlined.
If it works for you, I can submit that code and the updates that go with it in a PR so anyone stumbling on this in the future can see the interface call costs without the GC situation. Or if you want to integrate the code yourself, or integrate the info here in some other way, either sounds great to me.
It turns out a lot of the time the interface benchmark was taking was in GC related to the particular way interfaces are being used here, not cost that's inherent to all interface calls.
As background--since Go 1.4, when you wrap a non-pointer type like a uint64 in an interface, the interface value holds a pointer, and the actual int is allocated elsewhere. You can see the allocations by using MemStats with the original code: https://play.golang.org/p/UUlVZp7x-r.
If that cost were present every time someone makes an interface call (or most of the time) you'd want to count it, of course, but it often isn't.
You don't get an extra allocation when you store a pointer type in an interface var, which is probably the most common case, like when you put a
*bufio.Writeror*os.Filein anio.Writer.You also get only one extra allocation if you store a non-pointer type in an interface var once, then make lots of calls on it, like when you wrap a large slice in a
sort.Interfaceand callsort.Sorton it.After this benchmark got posted on Twitter and I noticed it was allocating for each iface call, I rigged up a variation where you call Add on an
*Int, so you don't get an alloc per call: https://play.golang.org/p/OgAcagFmZiThe results I got were 0 bytes allocated and 177.376219ms for the concrete calls, 16 bytes allocated and 210.502363ms for the indirect calls. I'm guessing the switch to using pointers was what made the concrete calls slower. Compiling with -gcflags -m shows the concrete call to Add still gets inlined.
If it works for you, I can submit that code and the updates that go with it in a PR so anyone stumbling on this in the future can see the interface call costs without the GC situation. Or if you want to integrate the code yourself, or integrate the info here in some other way, either sounds great to me.