Last active
October 3, 2017 16:19
-
-
Save gobwas/8c851c459220285ece8a997ce9e049e9 to your computer and use it in GitHub Desktop.
Alloc-free interface usage
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// 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)) | |
} | |
} |
Author
gobwas
commented
May 17, 2017
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