Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
28 changes: 18 additions & 10 deletions compress.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@ package iavl
import (
"encoding/binary"
"fmt"
"math/bits"
"unsafe"
)

type NodeExporter interface {
Expand Down Expand Up @@ -120,14 +122,23 @@ func deltaDecode(key, lastKey []byte) ([]byte, error) {
}

// diffOffset returns the index of first byte that's different in two bytes slice.
// Uses word-at-a-time comparison for better performance on longer slices.
func diffOffset(a, b []byte) int {
var off int
var l int
if len(a) < len(b) {
l = len(a)
} else {
l = len(b)
l := min(len(a), len(b))

// Compare 8 bytes at a time for longer slices
off := 0
for ; off+8 <= l; off += 8 {
av := *(*uint64)(unsafe.Pointer(&a[off]))
bv := *(*uint64)(unsafe.Pointer(&b[off]))
if av != bv {
// Find the first differing byte within this 8-byte word
xor := av ^ bv
return off + bits.TrailingZeros64(xor)/8
}
}

// Handle remaining bytes
for ; off < l; off++ {
if a[off] != b[off] {
break
Expand All @@ -137,8 +148,5 @@ func diffOffset(a, b []byte) int {
}

func maxInt64(a, b int64) int64 {
if a > b {
return a
}
return b
return max(a, b)
}
35 changes: 25 additions & 10 deletions internal/encoding/encoding.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,22 @@ var uvarintPool = &sync.Pool{
},
}

// uvarintSizeTable maps bits.Len64 (0-64) to varint encoded size (1-10).
// This replaces the division by 7 with a simple lookup.
var uvarintSizeTable = [65]int{
1, // 0 bits (handled separately, but included for safety)
1, 1, 1, 1, 1, 1, 1, // 1-7 bits -> 1 byte
2, 2, 2, 2, 2, 2, 2, // 8-14 bits -> 2 bytes
3, 3, 3, 3, 3, 3, 3, // 15-21 bits -> 3 bytes
4, 4, 4, 4, 4, 4, 4, // 22-28 bits -> 4 bytes
5, 5, 5, 5, 5, 5, 5, // 29-35 bits -> 5 bytes
6, 6, 6, 6, 6, 6, 6, // 36-42 bits -> 6 bytes
7, 7, 7, 7, 7, 7, 7, // 43-49 bits -> 7 bytes
8, 8, 8, 8, 8, 8, 8, // 50-56 bits -> 8 bytes
9, 9, 9, 9, 9, 9, 9, // 57-63 bits -> 9 bytes
10, // 64 bits -> 10 bytes
}

// decodeBytes decodes a varint length-prefixed byte slice, returning it along with the number
// of input bytes read.
// Assumes bz will not be mutated.
Expand Down Expand Up @@ -146,11 +162,12 @@ func EncodeUvarint(w io.Writer, u uint64) error {
}

// EncodeUvarintSize returns the byte size of the given integer as a varint.
// Uses a lookup table to avoid division.
func EncodeUvarintSize(u uint64) int {
if u == 0 {
return 1
}
return (bits.Len64(u) + 6) / 7
return uvarintSizeTable[bits.Len64(u)]
}

// EncodeVarint writes a varint-encoded integer to an io.Writer.
Expand Down Expand Up @@ -178,11 +195,8 @@ func EncodeVarint(w io.Writer, i int64) error {
}

func fVarintEncode(bw io.ByteWriter, x int64) error {
// Firstly convert it into a uvarint
ux := uint64(x) << 1
if x < 0 {
ux = ^ux
}
// Branchless zigzag encoding: convert signed to unsigned
ux := uint64(x<<1) ^ uint64(x>>63)
for ux >= 0x80 { // While there are 7 or more bits in the value, keep going
// Convert it into a byte then toggle the
// 7th bit to indicate that more bytes coming.
Expand All @@ -198,10 +212,11 @@ func fVarintEncode(bw io.ByteWriter, x int64) error {
}

// EncodeVarintSize returns the byte size of the given integer as a varint.
// Uses branchless zigzag encoding for better performance.
func EncodeVarintSize(i int64) int {
ux := uint64(i) << 1
if i < 0 {
ux = ^ux
}
// Branchless zigzag encoding: for i >= 0, ux = i << 1
// For i < 0, ux = ^(i << 1). The expression i >> 63 gives 0 for
// non-negative and -1 (all 1s) for negative, enabling branchless XOR.
ux := uint64(i<<1) ^ uint64(i>>63)
return EncodeUvarintSize(ux)
}
24 changes: 21 additions & 3 deletions node.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,15 +9,27 @@ import (
"encoding/binary"
"errors"
"fmt"
"hash"
"io"
"math"
"sync"

"github.com/cosmos/iavl/cache"

"github.com/cosmos/iavl/internal/color"
"github.com/cosmos/iavl/internal/encoding"
)

// sha256Pool is a pool for sha256.Hash objects to reduce allocations.
var sha256Pool = sync.Pool{
New: func() any {
return sha256.New()
},
}

// emptyHash is the hash of an empty input, computed once.
var emptyHash = sha256.New().Sum(nil)

const (
// ModeLegacyLeftNode is the mode for legacy left child in the node encoding/decoding.
ModeLegacyLeftNode = 0x01
Expand Down Expand Up @@ -428,11 +440,14 @@ func (node *Node) _hash(version int64) []byte {
return node.hash
}

h := sha256.New()
h := sha256Pool.Get().(hash.Hash)
h.Reset()
if err := node.writeHashBytes(h, version); err != nil {
sha256Pool.Put(h)
return nil
}
node.hash = h.Sum(nil)
sha256Pool.Put(h)

return node.hash
}
Expand All @@ -443,19 +458,22 @@ func (node *Node) _hash(version int64) []byte {
// to conform with RFC-6962.
func (node *Node) hashWithCount(version int64) []byte {
if node == nil {
return sha256.New().Sum(nil)
return emptyHash
}
if node.hash != nil {
return node.hash
}

h := sha256.New()
h := sha256Pool.Get().(hash.Hash)
h.Reset()
if err := node.writeHashBytesRecursively(h, version); err != nil {
// writeHashBytesRecursively doesn't return an error unless h.Write does,
// and hash.Hash.Write doesn't.
sha256Pool.Put(h)
panic(err)
}
node.hash = h.Sum(nil)
sha256Pool.Put(h)

return node.hash
}
Expand Down
5 changes: 1 addition & 4 deletions util.go
Original file line number Diff line number Diff line change
Expand Up @@ -56,8 +56,5 @@ func printNode(ndb *nodeDB, node *Node, indent int) error {
}

func maxInt8(a, b int8) int8 {
if a > b {
return a
}
return b
return max(a, b)
}
28 changes: 23 additions & 5 deletions v2/internal/encoding.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,22 @@ var uvarintPool = &sync.Pool{
},
}

// uvarintSizeTable maps bits.Len64 (0-64) to varint encoded size (1-10).
// This replaces the division by 7 with a simple lookup.
var uvarintSizeTable = [65]int{
1, // 0 bits (handled separately, but included for safety)
1, 1, 1, 1, 1, 1, 1, // 1-7 bits -> 1 byte
2, 2, 2, 2, 2, 2, 2, // 8-14 bits -> 2 bytes
3, 3, 3, 3, 3, 3, 3, // 15-21 bits -> 3 bytes
4, 4, 4, 4, 4, 4, 4, // 22-28 bits -> 4 bytes
5, 5, 5, 5, 5, 5, 5, // 29-35 bits -> 5 bytes
6, 6, 6, 6, 6, 6, 6, // 36-42 bits -> 6 bytes
7, 7, 7, 7, 7, 7, 7, // 43-49 bits -> 7 bytes
8, 8, 8, 8, 8, 8, 8, // 50-56 bits -> 8 bytes
9, 9, 9, 9, 9, 9, 9, // 57-63 bits -> 9 bytes
10, // 64 bits -> 10 bytes
}

// decodeBytes decodes a varint length-prefixed byte slice, returning it along with the number
// of input bytes read.
func DecodeBytes(bz []byte) ([]byte, int, error) {
Expand Down Expand Up @@ -130,11 +146,12 @@ func EncodeUvarint(w io.Writer, u uint64) error {
}

// EncodeUvarintSize returns the byte size of the given integer as a varint.
// Uses a lookup table to avoid division.
func EncodeUvarintSize(u uint64) int {
if u == 0 {
return 1
}
return (bits.Len64(u) + 6) / 7
return uvarintSizeTable[bits.Len64(u)]
}

// EncodeVarint writes a varint-encoded integer to an io.Writer.
Expand All @@ -158,10 +175,11 @@ func EncodeVarint(w io.Writer, i int64) error {
}

// EncodeVarintSize returns the byte size of the given integer as a varint.
// Uses branchless zigzag encoding for better performance.
func EncodeVarintSize(i int64) int {
ux := uint64(i) << 1
if i < 0 {
ux = ^ux
}
// Branchless zigzag encoding: for i >= 0, ux = i << 1
// For i < 0, ux = ^(i << 1). The expression i >> 63 gives 0 for
// non-negative and -1 (all 1s) for negative, enabling branchless XOR.
ux := uint64(i<<1) ^ uint64(i>>63)
return EncodeUvarintSize(ux)
}