Skip to content

Instantly share code, notes, and snippets.

@diamondburned
Last active October 29, 2020 05:09
Show Gist options
  • Save diamondburned/c369d13efda5c702a0e59874deee64bd to your computer and use it in GitHub Desktop.
Save diamondburned/c369d13efda5c702a0e59874deee64bd to your computer and use it in GitHub Desktop.
Handler benchmark: linked list vs slice map vs slice
package main
import (
"errors"
"reflect"
)
func main() {}
type handler struct {
event reflect.Type // underlying type; arg0 or chan underlying type
callback reflect.Value
isIface bool
chanclose reflect.Value // IsValid() if chan
}
func newHandler(unknown interface{}) (*handler, error) {
fnV := reflect.ValueOf(unknown)
fnT := fnV.Type()
// underlying event type
var handler = handler{
callback: fnV,
}
switch fnT.Kind() {
case reflect.Func:
if fnT.NumIn() != 1 {
return nil, errors.New("function can only accept 1 event as argument")
}
if fnT.NumOut() > 0 {
return nil, errors.New("function can't accept returns")
}
handler.event = fnT.In(0)
case reflect.Chan:
handler.event = fnT.Elem()
handler.chanclose = reflect.ValueOf(make(chan struct{}))
default:
return nil, errors.New("given interface is not a function or channel")
}
var kind = handler.event.Kind()
// Accept either pointer type or interface{} type
if kind != reflect.Ptr && kind != reflect.Interface {
return nil, errors.New("first argument is not pointer")
}
handler.isIface = kind == reflect.Interface
return &handler, nil
}
func (h handler) not(event reflect.Type) bool {
if h.isIface {
return !event.Implements(h.event)
}
return h.event != event
}
package main
import (
"container/list"
"fmt"
"math/rand"
"reflect"
"testing"
"time"
)
func init() { rand.Seed(time.Now().UnixNano()) }
func newDummyHandler() handler {
dumbFn := func(s *string) {
fmt.Println(*s)
}
h, err := newHandler(dumbFn)
if err != nil {
panic(err)
}
return *h
}
func _maybeNewDummyHandlers() []handler {
var handlers = make([]handler, 0, 10)
for i := 0; i < 10; i++ {
var dumbFn interface{}
if rand.Intn(2) == 1 {
dumbFn = func(s *string) { fmt.Println(*s) }
} else {
dumbFn = func(s *int) { fmt.Println(*s) }
}
h, err := newHandler(dumbFn)
if err != nil {
panic(err)
}
handlers = append(handlers, *h)
}
return handlers
}
func maybeNewDummyHandlers() []handler {
return append([]handler(nil), _maybeNewDummyHandlers()...)
}
var dumbType = reflect.TypeOf(new(int))
func BenchmarkLinkedListAdd(b *testing.B) {
var ll = list.New()
for i := 0; i < 10000; i++ {
ll.PushBack(newDummyHandler())
}
h, err := newHandler(func(i *int) { fmt.Println(*i) })
if err != nil {
panic(err)
}
ll.PushBack(*h)
b.ResetTimer()
for i := 0; i < b.N; i++ {
for elem := ll.Front(); elem != nil; elem = elem.Next() {
handler := elem.Value.(handler)
if handler.not(dumbType) {
continue
}
}
}
}
func BenchmarkSliceMapAdd(b *testing.B) {
var cbMap = map[uint64]handler{}
var order = []uint64{}
var index = uint64(0)
for i := 0; i < 10000; i++ {
cbMap[index] = newDummyHandler()
order = append(order, index)
index++
}
h, err := newHandler(func(i *int) { fmt.Println(*i) })
if err != nil {
panic(err)
}
cbMap[index] = *h
order = append(order, index)
index++
b.ResetTimer()
for i := 0; i < b.N; i++ {
for _, i := range order {
handler, ok := cbMap[i]
if !ok {
continue
}
if handler.not(dumbType) {
continue
}
}
}
}
func BenchmarkSliceAdd(b *testing.B) {
var sl []handler
for i := 0; i < 10000; i++ {
sl = append(sl, newDummyHandler())
}
h, err := newHandler(func(i *int) { fmt.Println(*i) })
if err != nil {
panic(err)
}
sl = append(sl, *h)
b.ResetTimer()
for i := 0; i < b.N; i++ {
for _, handler := range sl {
if handler.not(dumbType) {
continue
}
}
}
}
func BenchmarkLinkedListDelete(b *testing.B) {
for i := 0; i < b.N; i++ {
b.StopTimer()
var ll = list.New()
var elems = make(map[*list.Element]struct{}, 10)
for _, handler := range maybeNewDummyHandlers() {
elems[ll.PushBack(handler)] = struct{}{}
}
b.StartTimer()
for i := 0; i < 10; i++ {
var elem *list.Element
for e := range elems {
elem = e
break
}
delete(elems, elem)
ll.Remove(elem)
}
}
}
func BenchmarkSliceMapDelete(b *testing.B) {
for i := 0; i < b.N; i++ {
b.StopTimer()
var cbMap = make(map[uint64]handler, 10)
var order = make([]uint64, 0, 10)
var index = uint64(0)
var elems = make(map[handler]struct{}, 10)
for _, handler := range maybeNewDummyHandlers() {
cbMap[index] = handler
order = append(order, index)
index++
elems[handler] = struct{}{}
}
b.StartTimer()
for i := 0; i < 10; i++ {
var del uint64
for i := range cbMap {
del = i
break
}
delete(cbMap, del)
for i, ord := range order {
if ord == del {
order = append(order[:i], order[i+1:]...)
break
}
}
}
}
}
func BenchmarkSliceDelete(b *testing.B) {
for i := 0; i < b.N; i++ {
b.StopTimer()
var sl = make([]handler, 0, 10)
var elems = make(map[handler]struct{}, 10)
for _, handler := range maybeNewDummyHandlers() {
sl = append(sl, handler)
elems[handler] = struct{}{}
}
b.StartTimer()
for i := 0; i < 10; i++ {
var del handler
for h := range elems {
del = h
break
}
for i, handler := range sl {
if handler == del {
sl = append(sl[:i], sl[i+1:]...)
break
}
}
delete(elems, del)
}
}
}
BenchmarkLinkedListAdd-8 4933 253222 ns/op
BenchmarkSliceMapAdd-8 1375 815760 ns/op
BenchmarkSliceAdd-8 6985 143225 ns/op
BenchmarkLinkedListDelete-8 100000 1965 ns/op
BenchmarkSliceMapDelete-8 100000 1966 ns/op
BenchmarkSliceDelete-8 100000 2252 ns/op
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment