Skip to content

Instantly share code, notes, and snippets.

@gobwas
Last active October 3, 2017 16:19
Show Gist options
  • Star 2 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save gobwas/8c851c459220285ece8a997ce9e049e9 to your computer and use it in GitHub Desktop.
Save gobwas/8c851c459220285ece8a997ce9e049e9 to your computer and use it in GitHub Desktop.
Alloc-free interface usage
// This example shows how interfaces could be used without
// pushing inner data to heap.
//
// Note that it was checked with Go 1.8.3 and may become unuseful
// in the future releases of Go.
package main
import (
"encoding/binary"
"testing"
"unsafe"
)
type Fooer interface {
Foo()
}
func HandleFooer(f Fooer) {
f.Foo()
}
type Concrete byte
func (c Concrete) Foo() {}
func BenchmarkCallFooerUnsafe(b *testing.B) {
// First, we should retreive so called "itable" pointer
// for interface of dynamic type (Fooer, Concrete).
//
// The second part of interface f – data pointer; is not interesting for us.
var f Fooer = Concrete(0)
typ := *(*uint64)(unsafe.Pointer(&f))
for i := 0; i < b.N; i++ {
// Get the data pointer, the second part of an interface struct.
//
// Note that we getting pointer to the stack, and this is the main
// point of the example.
var c Concrete
ptr := uint64((uintptr)(unsafe.Pointer(&c)))
// Now we are ready to fill interface struct bytes.
// First 8 bytes (on 64-bit machines) are `itable` pointer.
// Second 8 bytes are interface data pointer.
var iface [16]byte
binary.LittleEndian.PutUint64(iface[:8], typ)
binary.LittleEndian.PutUint64(iface[8:], ptr)
// Cast stack based bytes to the stack based interface.
// This works because escape analysis could not make
// decision on unsafe casted values.
f := *(*Fooer)(unsafe.Pointer(&iface))
HandleFooer(f)
}
}
func BenchmarkPassFooer(b *testing.B) {
for i := 0; i < b.N; i++ {
var c Concrete
// In this case &c escapes to heap, because escape analysis
// does not know what happen inside Fooer.Foo().
// See https://github.com/golang/go/issues/19361
HandleFooer(Fooer(&c))
}
}
@gobwas
Copy link
Author

gobwas commented May 17, 2017

BenchmarkCallFooerUnsafe-4      100000000               15.3 ns/op             0 B/op          0 allocs/op
BenchmarkPassFooer-4            100000000               19.8 ns/op             1 B/op          1 allocs/op

@grim-yawn
Copy link

For Go 1.9 works as well.

200000000	         5.67 ns/op	       0 B/op	       0 allocs/op
50000000	        20.4 ns/op	       1 B/op	       1 allocs/op

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment