go-ethereum/triedb/pathdb/history_index_block_test.go
Delweng a487729d83
triedb/pathdb: improve the performance of parse index block (#32219)
The implementation of `parseIndexBlock` used a reverse loop with slice
appends to build the restart points, which was less cache-friendly and
involved unnecessary allocations and operations. In this PR we change
the implementation to read and validate the restart points in one single
forward loop.

Here is the benchmark test:

```bash
go test -benchmem -bench=BenchmarkParseIndexBlock ./triedb/pathdb/
```

The result as below:

```
benchmark                      old ns/op     new ns/op     delta
BenchmarkParseIndexBlock-8     52.9          37.5          -29.05%
```

about 29% improvements

---------

Signed-off-by: jsvisa <delweng@gmail.com>
2025-07-17 11:07:22 +08:00

234 lines
5.8 KiB
Go

// Copyright 2025 The go-ethereum Authors
// This file is part of the go-ethereum library.
//
// The go-ethereum library is free software: you can redistribute it and/or modify
// it under the terms of the GNU Lesser General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// The go-ethereum library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public License
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/
package pathdb
import (
"math"
"math/rand"
"slices"
"sort"
"testing"
)
func TestBlockReaderBasic(t *testing.T) {
elements := []uint64{
1, 5, 10, 11, 20,
}
bw, _ := newBlockWriter(nil, newIndexBlockDesc(0))
for i := 0; i < len(elements); i++ {
bw.append(elements[i])
}
br, err := newBlockReader(bw.finish())
if err != nil {
t.Fatalf("Failed to construct the block reader, %v", err)
}
cases := []struct {
value uint64
result uint64
}{
{0, 1},
{1, 5},
{10, 11},
{19, 20},
{20, math.MaxUint64},
{21, math.MaxUint64},
}
for _, c := range cases {
got, err := br.readGreaterThan(c.value)
if err != nil {
t.Fatalf("Unexpected error, got %v", err)
}
if got != c.result {
t.Fatalf("Unexpected result, got %v, wanted %v", got, c.result)
}
}
}
func TestBlockReaderLarge(t *testing.T) {
var elements []uint64
for i := 0; i < 1000; i++ {
elements = append(elements, rand.Uint64())
}
slices.Sort(elements)
bw, _ := newBlockWriter(nil, newIndexBlockDesc(0))
for i := 0; i < len(elements); i++ {
bw.append(elements[i])
}
br, err := newBlockReader(bw.finish())
if err != nil {
t.Fatalf("Failed to construct the block reader, %v", err)
}
for i := 0; i < 100; i++ {
value := rand.Uint64()
pos := sort.Search(len(elements), func(i int) bool {
return elements[i] > value
})
got, err := br.readGreaterThan(value)
if err != nil {
t.Fatalf("Unexpected error, got %v", err)
}
if pos == len(elements) {
if got != math.MaxUint64 {
t.Fatalf("Unexpected result, got %d, wanted math.MaxUint64", got)
}
} else if got != elements[pos] {
t.Fatalf("Unexpected result, got %d, wanted %d", got, elements[pos])
}
}
}
func TestBlockWriterBasic(t *testing.T) {
bw, _ := newBlockWriter(nil, newIndexBlockDesc(0))
if !bw.empty() {
t.Fatal("expected empty block")
}
bw.append(2)
if err := bw.append(1); err == nil {
t.Fatal("out-of-order insertion is not expected")
}
for i := 0; i < 10; i++ {
bw.append(uint64(i + 3))
}
bw, err := newBlockWriter(bw.finish(), newIndexBlockDesc(0))
if err != nil {
t.Fatalf("Failed to construct the block writer, %v", err)
}
for i := 0; i < 10; i++ {
if err := bw.append(uint64(i + 100)); err != nil {
t.Fatalf("Failed to append value %d: %v", i, err)
}
}
bw.finish()
}
func TestBlockWriterDelete(t *testing.T) {
bw, _ := newBlockWriter(nil, newIndexBlockDesc(0))
for i := 0; i < 10; i++ {
bw.append(uint64(i + 1))
}
// Pop unknown id, the request should be rejected
if err := bw.pop(100); err == nil {
t.Fatal("Expect error to occur for unknown id")
}
for i := 10; i >= 1; i-- {
if err := bw.pop(uint64(i)); err != nil {
t.Fatalf("Unexpected error for element popping, %v", err)
}
empty := i == 1
if empty != bw.empty() {
t.Fatalf("Emptiness is not matched, want: %T, got: %T", empty, bw.empty())
}
newMax := uint64(i - 1)
if bw.desc.max != newMax {
t.Fatalf("Maxmium element is not matched, want: %d, got: %d", newMax, bw.desc.max)
}
}
}
func TestBlcokWriterDeleteWithData(t *testing.T) {
elements := []uint64{
1, 5, 10, 11, 20,
}
bw, _ := newBlockWriter(nil, newIndexBlockDesc(0))
for i := 0; i < len(elements); i++ {
bw.append(elements[i])
}
// Re-construct the block writer with data
desc := &indexBlockDesc{
id: 0,
max: 20,
entries: 5,
}
bw, err := newBlockWriter(bw.finish(), desc)
if err != nil {
t.Fatalf("Failed to construct block writer %v", err)
}
for i := len(elements) - 1; i > 0; i-- {
if err := bw.pop(elements[i]); err != nil {
t.Fatalf("Failed to pop element, %v", err)
}
newTail := elements[i-1]
// Ensure the element can still be queried with no issue
br, err := newBlockReader(bw.finish())
if err != nil {
t.Fatalf("Failed to construct the block reader, %v", err)
}
cases := []struct {
value uint64
result uint64
}{
{0, 1},
{1, 5},
{10, 11},
{19, 20},
{20, math.MaxUint64},
{21, math.MaxUint64},
}
for _, c := range cases {
want := c.result
if c.value >= newTail {
want = math.MaxUint64
}
got, err := br.readGreaterThan(c.value)
if err != nil {
t.Fatalf("Unexpected error, got %v", err)
}
if got != want {
t.Fatalf("Unexpected result, got %v, wanted %v", got, want)
}
}
}
}
func TestCorruptedIndexBlock(t *testing.T) {
bw, _ := newBlockWriter(nil, newIndexBlockDesc(0))
for i := 0; i < 10; i++ {
bw.append(uint64(i + 1))
}
buf := bw.finish()
// Mutate the buffer manually
buf[len(buf)-1]++
_, err := newBlockWriter(buf, newIndexBlockDesc(0))
if err == nil {
t.Fatal("Corrupted index block data is not detected")
}
}
// BenchmarkParseIndexBlock benchmarks the performance of parseIndexBlock.
func BenchmarkParseIndexBlock(b *testing.B) {
// Generate a realistic index block blob
bw, _ := newBlockWriter(nil, newIndexBlockDesc(0))
for i := 0; i < 4096; i++ {
bw.append(uint64(i * 2))
}
blob := bw.finish()
b.ResetTimer()
for i := 0; i < b.N; i++ {
_, _, err := parseIndexBlock(blob)
if err != nil {
b.Fatalf("parseIndexBlock failed: %v", err)
}
}
}