Skip to content

Commit

Permalink
Release 0.11.0 (#112)
Browse files Browse the repository at this point in the history
See full changelog here:
https://github.com/tendermint/iavl/blob/develop/CHANGELOG.md#0110-september-7-2018

* Update to CircleCI 2.0 (#108)

* Use 8 bytes to store int64 components of database keys  (#107)

* Introduce KeyFormat that uses a full 8 bytes for int64 values and avoids string manipulatio/scanning

* Add node key and orphan key benchmark

* Fix various issue from PR: punctuation, add overflow test, and improve
scan function

* Remove architecture dependent getter functions (#96)

* Remove architecture dependent getter functions

* update changelog

* Prep Release 0.11.0 (#111)

* Bump version and update change-log
  • Loading branch information
liamsi authored Sep 8, 2018
1 parent e5726c0 commit 3acc91f
Show file tree
Hide file tree
Showing 16 changed files with 365 additions and 146 deletions.
16 changes: 16 additions & 0 deletions .circleci/config.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
version: 2
jobs:
test:
docker:
- image: circleci/golang:1.10.3
working_directory: /go/src/github.com/tendermint/iavl
steps:
- checkout
- run: dep ensure -v
- run: make test

workflows:
version: 2
test:
jobs:
- test
15 changes: 15 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,20 @@
# Changelog

## 0.11.0 (September 7, 2018)

BREAKING CHANGES

- Changed internal database key format to store int64 key components in a full 8-byte fixed width ([#107])
- Removed some architecture dependent methods (e.g., use `Get` instead of `Get64` etc) ([#96])

IMPROVEMENTS

- Database key format avoids use of fmt.Sprintf fmt.Sscanf leading to ~10% speedup in benchmark BenchmarkTreeLoadAndDelete ([#107], thanks to [@silasdavis])

[#107]: https://github.com/tendermint/iavl/pull/107
[@silasdavis]: https://github.com/silasdavis
[#96]: https://github.com/tendermint/iavl/pull/96

## 0.10.0

BREAKING CHANGES
Expand Down
6 changes: 3 additions & 3 deletions Makefile
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
GOTOOLS := github.com/mitchellh/gox \
github.com/golang/dep/cmd/dep
GOTOOLS := github.com/golang/dep/cmd/dep

PDFFLAGS := -pdf --nodefraction=0.1

all: get_vendor_deps test

test:
GOCACHE=off go test -v --race `glide novendor`
GOCACHE=off go test -v --race

tools:
go get -u -v $(GOTOOLS)
Expand Down
20 changes: 10 additions & 10 deletions basic_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ func TestBasic(t *testing.T) {

// Test 0x00
{
idx, val := tree.Get64([]byte{0x00})
idx, val := tree.Get([]byte{0x00})
if val != nil {
t.Errorf("Expected no value to exist")
}
Expand All @@ -46,7 +46,7 @@ func TestBasic(t *testing.T) {

// Test "1"
{
idx, val := tree.Get64([]byte("1"))
idx, val := tree.Get([]byte("1"))
if val == nil {
t.Errorf("Expected value to exist")
}
Expand All @@ -60,7 +60,7 @@ func TestBasic(t *testing.T) {

// Test "2"
{
idx, val := tree.Get64([]byte("2"))
idx, val := tree.Get([]byte("2"))
if val == nil {
t.Errorf("Expected value to exist")
}
Expand All @@ -74,7 +74,7 @@ func TestBasic(t *testing.T) {

// Test "4"
{
idx, val := tree.Get64([]byte("4"))
idx, val := tree.Get([]byte("4"))
if val != nil {
t.Errorf("Expected no value to exist")
}
Expand All @@ -88,7 +88,7 @@ func TestBasic(t *testing.T) {

// Test "6"
{
idx, val := tree.Get64([]byte("6"))
idx, val := tree.Get([]byte("6"))
if val != nil {
t.Errorf("Expected no value to exist")
}
Expand Down Expand Up @@ -237,7 +237,7 @@ func TestIntegration(t *testing.T) {
if !updated {
t.Error("should have been updated")
}
if tree.Size() != i+1 {
if tree.Size() != int64(i+1) {
t.Error("size was wrong", tree.Size(), i+1)
}
}
Expand All @@ -249,7 +249,7 @@ func TestIntegration(t *testing.T) {
if has := tree.Has([]byte(randstr(12))); has {
t.Error("Table has extra key")
}
if _, val := tree.Get64([]byte(r.key)); string(val) != string(r.value) {
if _, val := tree.Get([]byte(r.key)); string(val) != string(r.value) {
t.Error("wrong value")
}
}
Expand All @@ -267,12 +267,12 @@ func TestIntegration(t *testing.T) {
if has := tree.Has([]byte(randstr(12))); has {
t.Error("Table has extra key")
}
_, val := tree.Get64([]byte(r.key))
_, val := tree.Get([]byte(r.key))
if string(val) != string(r.value) {
t.Error("wrong value")
}
}
if tree.Size() != len(records)-(i+1) {
if tree.Size() != int64(len(records)-(i+1)) {
t.Error("size was wrong", tree.Size(), (len(records) - (i + 1)))
}
}
Expand Down Expand Up @@ -382,7 +382,7 @@ func TestPersistence(t *testing.T) {
t2 := NewMutableTree(db, 0)
t2.Load()
for key, value := range records {
_, t2value := t2.Get64([]byte(key))
_, t2value := t2.Get([]byte(key))
if string(t2value) != value {
t.Fatalf("Invalid value. Expected %v, got %v", value, t2value)
}
Expand Down
21 changes: 0 additions & 21 deletions circle.yml

This file was deleted.

20 changes: 0 additions & 20 deletions glide.yaml

This file was deleted.

31 changes: 5 additions & 26 deletions immutable_tree.go
Original file line number Diff line number Diff line change
Expand Up @@ -39,32 +39,20 @@ func (t *ImmutableTree) String() string {
}

// Size returns the number of leaf nodes in the tree.
func (t *ImmutableTree) Size() int {
return int(t.Size64())
}

func (t *ImmutableTree) Size64() int64 {
func (t *ImmutableTree) Size() int64 {
if t.root == nil {
return 0
}
return t.root.size
}

// Version returns the version of the tree.
func (t *ImmutableTree) Version() int {
return int(t.Version64())
}

func (t *ImmutableTree) Version64() int64 {
func (t *ImmutableTree) Version() int64 {
return t.version
}

// Height returns the height of the tree.
func (t *ImmutableTree) Height() int {
return int(t.Height8())
}

func (t *ImmutableTree) Height8() int8 {
func (t *ImmutableTree) Height() int8 {
if t.root == nil {
return 0
}
Expand Down Expand Up @@ -98,24 +86,15 @@ func (t *ImmutableTree) hashWithCount() ([]byte, int64) {

// Get returns the index and value of the specified key if it exists, or nil
// and the next index, if it doesn't.
func (t *ImmutableTree) Get(key []byte) (index int, value []byte) {
index64, value := t.Get64(key)
return int(index64), value
}

func (t *ImmutableTree) Get64(key []byte) (index int64, value []byte) {
func (t *ImmutableTree) Get(key []byte) (index int64, value []byte) {
if t.root == nil {
return 0, nil
}
return t.root.get(t, key)
}

// GetByIndex gets the key and value at the specified index.
func (t *ImmutableTree) GetByIndex(index int) (key []byte, value []byte) {
return t.GetByIndex64(int64(index))
}

func (t *ImmutableTree) GetByIndex64(index int64) (key []byte, value []byte) {
func (t *ImmutableTree) GetByIndex(index int64) (key []byte, value []byte) {
if t.root == nil {
return nil, nil
}
Expand Down
144 changes: 144 additions & 0 deletions key_format.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,144 @@
package iavl

import (
"encoding/binary"
"fmt"
)

// Provides a fixed-width lexicographically sortable []byte key format
type KeyFormat struct {
prefix byte
layout []int
length int
}

// Create a []byte key format based on a single byte prefix and fixed width key segments each of whose length is
// specified by by the corresponding element of layout.
//
// For example, to store keys that could index some objects by a version number and their SHA256 hash using the form:
// 'c<version uint64><hash [32]byte>' then you would define the KeyFormat with:
//
// var keyFormat = NewKeyFormat('c', 8, 32)
//
// Then you can create a key with:
//
// func ObjectKey(version uint64, objectBytes []byte) []byte {
// hasher := sha256.New()
// hasher.Sum(nil)
// return keyFormat.Key(version, hasher.Sum(nil))
// }
func NewKeyFormat(prefix byte, layout ...int) *KeyFormat {
// For prefix byte
length := 1
for _, l := range layout {
length += int(l)
}
return &KeyFormat{
prefix: prefix,
layout: layout,
length: length,
}
}

// Format the byte segments into the key format - will panic if the segment lengths do not match the layout.
func (kf *KeyFormat) KeyBytes(segments ...[]byte) []byte {
key := make([]byte, kf.length)
key[0] = kf.prefix
n := 1
for i, s := range segments {
l := kf.layout[i]
if len(s) > l {
panic(fmt.Errorf("length of segment %X provided to KeyFormat.KeyBytes() is longer than the %d bytes "+
"required by layout for segment %d", s, l, i))
}
n += l
// Big endian so pad on left if not given the full width for this segment
copy(key[n-len(s):n], s)
}
return key[:n]
}

// Format the args passed into the key format - will panic if the arguments passed do not match the length
// of the segment to which they correspond. When called with no arguments returns the raw prefix (useful as a start
// element of the entire keys space when sorted lexicographically).
func (kf *KeyFormat) Key(args ...interface{}) []byte {
if len(args) > len(kf.layout) {
panic(fmt.Errorf("KeyFormat.Key() is provided with %d args but format only has %d segments",
len(args), len(kf.layout)))
}
segments := make([][]byte, len(args))
for i, a := range args {
segments[i] = format(a)
}
return kf.KeyBytes(segments...)
}

// Reads out the bytes associated with each segment of the key format from key.
func (kf *KeyFormat) ScanBytes(key []byte) [][]byte {
segments := make([][]byte, len(kf.layout))
n := 1
for i, l := range kf.layout {
n += l
if n > len(key) {
return segments[:i]
}
segments[i] = key[n-l : n]
}
return segments
}

// Extracts the segments into the values pointed to by each of args. Each arg must be a pointer to int64, uint64, or
// []byte, and the width of the args must match layout.
func (kf *KeyFormat) Scan(key []byte, args ...interface{}) {
segments := kf.ScanBytes(key)
if len(args) > len(segments) {
panic(fmt.Errorf("KeyFormat.Scan() is provided with %d args but format only has %d segments in key %X",
len(args), len(segments), key))
}
for i, a := range args {
scan(a, segments[i])
}
}

// Return the prefix as a string.
func (kf *KeyFormat) Prefix() string {
return string([]byte{kf.prefix})
}

func scan(a interface{}, value []byte) {
switch v := a.(type) {
case *int64:
// Negative values will be mapped correctly when read in as uint64 and then type converted
*v = int64(binary.BigEndian.Uint64(value))
case *uint64:
*v = binary.BigEndian.Uint64(value)
case *[]byte:
*v = value
default:
panic(fmt.Errorf("KeyFormat scan() does not support scanning value of type %T: %v", a, a))
}
}

func format(a interface{}) []byte {
switch v := a.(type) {
case uint64:
return formatUint64(v)
case int64:
return formatUint64(uint64(v))
// Provide formatting from int,uint as a convenience to avoid casting arguments
case uint:
return formatUint64(uint64(v))
case int:
return formatUint64(uint64(v))
case []byte:
return v
default:
panic(fmt.Errorf("KeyFormat format() does not support formatting value of type %T: %v", a, a))
}
}

func formatUint64(v uint64) []byte {
bs := make([]byte, 8)
binary.BigEndian.PutUint64(bs, v)
return bs
}
Loading

0 comments on commit 3acc91f

Please sign in to comment.