Avoid heap allocation when passing certain standard Go types to functions accepting empty interfaces

1,138 views
Skip to first unread message

val...@gmail.com

unread,
Apr 15, 2016, 12:22:28 PM4/15/16
to golang-dev
Hi all.

It would be great eliminating heap allocations when passing the following standard Go types to functions accepting empty interfaces:
- 64-bit int (int64, uint64, int on x64)
- float64
- string
- slice

These heap allocations may slow down code with the following quite common functions accepting empty interface arguments:
- fmt.Printf-like functions
- Query and Exec functions from database/sql
- sync.Pool.Put

I understand that such types as string and slice cannot fit into word-sized 'data' field from interface struct. But the interface struct could be extended by two additional words when string or slice is passed to an interface{} function argument. As for []interface{} type, which is frequently used for passing arguments in Printf-like variadic functions, it could always point to an array of 4-word-sized interface structs. I believe extending interface{} size from 2 words to 4 words for passing function arguments will result in higher performance comparing to a heap allocation per each such argument as it works today.

Below is a memory profile dump generated with 'go tool pprof --alloc_objects' from this benchmark code .

         .          .     23: for i := 0; i < b.N; i++ {
         .   18274275     24: n += f(m)
         .    4128831     25: n += f(ch)
         .    1998878     26: n += f(n32)
   1966110    4259905     27: n += f(n64)
   1638425    3735609     28: n += f(nn)
         .    1703962     29: n += f(f32)
   2490406    4751432     30: n += f(f64)
   4620358    7307375     31: n += f(str)
   4030587    7880944     32: n += f(slice)
         .          .     33:
         .   17695343     34: n += f(&m)
         .    3735609     35: n += f(&ch)
         .    3604535     36: n += f(&n32)
         .    4259905     37: n += f(&n64)
         .    4751432     38: n += f(&nn)
         .    4063294     39: n += f(&f32)
         .    4128831     40: n += f(&f64)
         .    4128831     41: n += f(&str)
         .   10535197     42: n += f(&slice)
         .          .     43: }

Russ Cox

unread,
Apr 15, 2016, 12:33:34 PM4/15/16
to val...@gmail.com, golang-dev
We looked into this back when we evicted one-word scalar values from interfaces, and it seemed to us then (and still seems to me now) that making all interface values 3 words long (or 4 if you want to hold slices too) is probably a net loss. There are _many_ places where interfaces are used, overwhelmingly holding single-pointer values, and paying an extra one or two words per interface in every data structure everywhere in the program seems likely to cost far more than what is saved in Printf. If you have evidence to the contrary, I'd love to see it.

It is intriguing to think about making interface{} a different size from interfaces with methods (like io.Reader), but I worry about the linguistic pressure that would exert: it would give people reasons to avoid method-based interfaces for interface{}, which is likely the opposite from good practice as far as writing clear programs.

Russ

--
You received this message because you are subscribed to the Google Groups "golang-dev" group.
To unsubscribe from this group and stop receiving emails from it, send an email to golang-dev+...@googlegroups.com.
For more options, visit https://groups.google.com/d/optout.

Keith Randall

unread,
Apr 15, 2016, 12:41:56 PM4/15/16
to Russ Cox, David Chase, val...@gmail.com, golang-dev
We do avoid allocations in cases where the interface doesn't escape.  So code like this:

var b bool
func f(i interface{}) {
b = (i == nil)
}
func main() {
var x int64 = 3
f(x)
}

The compiler will notice that i does not escape from f, and thus main will allocate the backing store for i on the stack.  No malloc needed.

This optimization does not trigger for Printf's arguments, unfortunately.  In an ideal world, the compiler should be able to figure out that it is ok.  It's probably too hard though, as Printf may call String() and other user-provided code that could (but probably never does) escape its argument.

Reply all
Reply to author
Forward
0 new messages