freetree

package module
v0.0.0-...-690ab69 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Nov 30, 2015 License: MIT Imports: 5 Imported by: 0

README

FreeTree Status Build Status GoDoc

Generic binary search tree with zero GC overhead, for golang. Built on mmm.

What you should know

FreeTree was developed mainly as a proof-of-concept for mmm; that is, to demonstrate how you can use mmm to avoid GC overhead in "pointer-heavy" Go software, without modifying nor complexifying your original design (i.e. entirely redesigning your software to avoid the use of pointers, which often leads to overly complex and less maintainable code).

Although I do use it for some big immutable caches of mine, FreeTree's API is quite incomplete and could certainly be better designed; especially during initialization where a lot of unnecessary copying could probably be avoided. Feel free to improve it :)

Install

go get -u github.com/teh-cmc/freetree

Example

Here's a simple example of usage (code here):

package main

import (
	"fmt"
	"log"

	"github.com/teh-cmc/freetree"
)

// -----------------------------------------------------------------------------

/////
// Simple example of usage
//
//   go run examples/simple.go
//
/////

// Int is a simple integer that implements the Comparable interface.
type Int int

// Less returns true if `i1` < `i2`.
func (i1 Int) Less(i2 freetree.Comparable) bool { return i1 < i2.(Int) }

func main() {
	// build a new SimpleTree and insert 3 integers in it
	st := freetree.NewSimpleTree().Insert(Int(17), Int(66), Int(42))

	// print 42
	fmt.Println(st.Ascend(Int(42)))
	// print <nil>
	fmt.Println(st.Ascend(Int(43)))

	// print [42 17 66]
	fmt.Println(st.Flatten())

	// rebalance the tree
	st.Rebalance()

	// print [17 66 42]
	fmt.Println(st.Flatten())

	// build a new FreeTree using the data from the SimpleTree
	ft, err := freetree.NewFreeTree(st)
	if err != nil {
		log.Fatal(err)
	}

	// delete the SimpleTree and clear the garbage
	st = st.DeleteGC()

	// print 42
	fmt.Println(ft.Ascend(Int(42)))
	// print <nil>
	fmt.Println(ft.Ascend(Int(43)))

	// print [17 66 42]
	fmt.Println(ft.Flatten())

	// delete the FreeTree
	ft.Delete()
}

Demonstration

Complete code for the following demonstration is available here.

All of the results shown below were computed using a DELL XPS 15-9530 (i7-4712HQ@2.30GHz).

Case A: normal BST, 10 million integers

Let's look at GC performances while storing 10 million integers in a "classic" binary search tree:

// build 10 million integers
ints := make([]freetree.Comparable, 10*1e6)
// init our integers
for i := range ints {
	ints[i] = Int(i)
}

// build a new BST and insert our 10 million integers in it
// our integers are pre-sorted, so the tree will be perfectly balanced (because
// that's how FreeTree's insert API works)
st := freetree.NewSimpleTree().InsertArray(ints)

for i := 0; i < 5; i++ {
	// randomly print one of our integers to make sure it's all working
	// as expected, and to prevent them from being optimized away
	fmt.Printf("\tvalue @ index %d: %d\n", i*1e4, st.Ascend(Int(i*1e4)))

	// run GC
	now := time.Now().UnixNano()
	runtime.GC()
	fmt.Printf("\tGC time (normal BST, 10 million integers): %d us\n", (time.Now().UnixNano()-now)/1e3)
}

This prints:

value @ index 0: 0
GC time (normal BST, 10 million integers): 276821 us
value @ index 10000: 10000
GC time (normal BST, 10 million integers): 278205 us
value @ index 20000: 20000
GC time (normal BST, 10 million integers): 286721 us
value @ index 30000: 30000
GC time (normal BST, 10 million integers): 277796 us
value @ index 40000: 40000
GC time (normal BST, 10 million integers): 277528 us

That's an average ~278ms per GC call. Let's move to case B where we'll store 10 million integers in a FreeTree.

Case B: FreeTree, 10 million integers

Let's look at GC performances while storing 10 million integers in a FreeTree (i.e. a binary search tree with no GC overhead):

// build a new FreeTree using the data from our SimpleTree
ft, err := freetree.NewFreeTree(st)
if err != nil {
	log.Fatal(err)
}

// delete the SimpleTree and get rid of the garbage
st = st.DeleteGC()

for i := 0; i < 5; i++ {
	// randomly print one of our integers to make sure it's all working
	// as expected
	fmt.Printf("\tvalue @ index %d: %d\n", i*1e4, ft.Ascend(Int(i*1e4)))

	// run GC
	now := time.Now().UnixNano()
	runtime.GC()
	fmt.Printf("\tGC time (FreeTree, 10 million integers): %d us\n", (time.Now().UnixNano()-now)/1e3)
}

This prints:

value @ index 0: 0
GC time (FreeTree, 10 million integers): 3287 us
value @ index 10000: 10000
GC time (FreeTree, 10 million integers): 3527 us
value @ index 20000: 20000
GC time (FreeTree, 10 million integers): 3346 us
value @ index 30000: 30000
GC time (FreeTree, 10 million integers): 3447 us
value @ index 40000: 40000
GC time (FreeTree, 10 million integers): 3104 us

We went from a ~278ms average to a ~3.3ms average; and most importantly, we did that without modifying our design: internally, both trees' node structures and Ascend API work the same way.

License License

The MIT License (MIT) - see LICENSE for more details

Copyright (c) 2015 Clement 'cmc' Rey cr.rey.clement@gmail.com

Documentation

Overview

Example (Simple_usage)
// build a new SimpleTree and insert 3 integers in it
st := NewSimpleTree().Insert(Int(17), Int(66), Int(42))

// print 42
fmt.Println(st.Ascend(Int(42)))
// print <nil>
fmt.Println(st.Ascend(Int(43)))

// print [42 17 66]
fmt.Println(st.Flatten())

// rebalance the tree
st.Rebalance()

// print [17 66 42]
fmt.Println(st.Flatten())

// build a new FreeTree using the data from the SimpleTree
ft, err := NewFreeTree(st)
if err != nil {
	log.Fatal(err)
}

// delete the SimpleTree and clear the garbage
st = st.DeleteGC()

// print 42
fmt.Println(ft.Ascend(Int(42)))
// print <nil>
fmt.Println(ft.Ascend(Int(43)))

// print [17 66 42]
fmt.Println(ft.Flatten())

// delete the FreeTree
ft.Delete()
Output:

42
<nil>
[42 17 66]
[17 66 42]
42
<nil>
[17 66 42]

Index

Examples

Constants

This section is empty.

Variables

This section is empty.

Functions

This section is empty.

Types

type Comparable

type Comparable interface {
	// Less returns true if `c1` < `c2`
	Less(c2 Comparable) bool
}

Comparable exposes a single Less method.

type ComparableArray

type ComparableArray []Comparable

ComparableArray represents a sortable array of Comparables.

func (ComparableArray) Len

func (ca ComparableArray) Len() int

Len returns the length of the array.

func (ComparableArray) Less

func (ca ComparableArray) Less(i, j int) bool

Less returns true if `ca[i]` < `ca[j]`.

func (ComparableArray) Swap

func (ca ComparableArray) Swap(i, j int)

Swap swaps the values of `ca[i]` and `ca[j]`.

type FreeTree

type FreeTree struct {
	// contains filtered or unexported fields
}

FreeTree implements a binary search tree with zero GC overhead.

func NewFreeTree

func NewFreeTree(st *SimpleTree) (*FreeTree, error)

NewFreeTree returns a new FreeTree using the data from a supplied SimpleTree.

func (FreeTree) Ascend

func (ft FreeTree) Ascend(pivot Comparable) Comparable

Ascend returns the first element in the tree that is == `pivot`.

func (*FreeTree) Delete

func (ft *FreeTree) Delete() *FreeTree

Delete deletes the memory chunks associated with the tree.

func (FreeTree) Flatten

func (ft FreeTree) Flatten() ComparableArray

Flatten returns the content of the tree as a ComparableArray.

type SimpleTree

type SimpleTree struct {
	// contains filtered or unexported fields
}

SimpleTree implements a simple binary search tree.

func NewSimpleTree

func NewSimpleTree() *SimpleTree

NewSimpleTree returns an empty SimpleTree.

func (SimpleTree) Ascend

func (st SimpleTree) Ascend(pivot Comparable) Comparable

Ascend returns the first element in the tree that is == `pivot`.

func (*SimpleTree) Delete

func (st *SimpleTree) Delete() *SimpleTree

Delete sets all the pointers in the tree to nil.

I strongly suggest running the garbage collector and scavenger once it's done.

runtime.GC()
debug.FreeOSMemory()

Alternatively, you can use DeleteGC().

func (*SimpleTree) DeleteGC

func (st *SimpleTree) DeleteGC() *SimpleTree

DeleteGC sets all the pointers of the tree to nil and runs the garbage collector.

func (SimpleTree) Flatten

func (st SimpleTree) Flatten() ComparableArray

Flatten returns the content of the tree as a ComparableArray.

func (*SimpleTree) Insert

func (st *SimpleTree) Insert(cs ...Comparable) *SimpleTree

Insert inserts the given Comparables in the tree. It does not rebalance the tree, use Rebalance() for that.

If the tree is currently empty and the passed-in Comparable are already sorted in increasing order, the tree will be perfectly balanced. This means you don't have to Rebalance() the tree if you've inserted all your pre-sorted data in one Insert() call.

func (*SimpleTree) InsertArray

func (st *SimpleTree) InsertArray(ca ComparableArray) *SimpleTree

InsertArray is a helper to use Insert() with a ComparableArray.

func (*SimpleTree) Rebalance

func (st *SimpleTree) Rebalance() *SimpleTree

Rebalance rebalances the tree to guarantee O(log(n)) search complexity.

Rebalancing is implemented as straightforwardly as possible: it's dumb. I strongly suggest running the garbage collector and scavenger once it's done.

runtime.GC()
debug.FreeOSMemory()

Alternatively, you can use RebalanceGC().

func (*SimpleTree) RebalanceGC

func (st *SimpleTree) RebalanceGC() *SimpleTree

RebalanceGC rebalances the tree and runs the garbage collector.

Directories

Path Synopsis

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL