Skip to content

Commit 7ce4593

Browse files
v1/ast: Implement lazy hash computation for Array
Refactor Array structure to use lazy hash evaluation instead of maintaining a separate hashs slice, reducing memory overhead by 8*N bytes per array. Key changes: - Remove hashs []int slice from Array struct - Add hashValid bool flag to track hash computation state - Implement lazy hash computation in Hash() method - Optimize Append() with incremental hash updates when hash is already computed - Update Copy(), Sorted(), Slice() to work with new hash model - Simplify rehash() to just invalidate the hash cache This optimization reduces memory allocations while maintaining performance through intelligent caching and incremental updates.
1 parent 7df8ce1 commit 7ce4593

File tree

1 file changed

+43
-29
lines changed

1 file changed

+43
-29
lines changed

v1/ast/term.go

Lines changed: 43 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -1346,32 +1346,34 @@ func ArrayTerm(a ...*Term) *Term {
13461346
// NewArray creates an Array with the terms provided. The array will
13471347
// use the provided term slice.
13481348
func NewArray(a ...*Term) *Array {
1349-
hs := make([]int, len(a))
1350-
for i, e := range a {
1351-
hs[i] = e.Value.Hash()
1349+
arr := &Array{
1350+
elems: a,
1351+
ground: termSliceIsGround(a),
1352+
hashValid: false, // hash will be computed on first access
13521353
}
1353-
arr := &Array{elems: a, hashs: hs, ground: termSliceIsGround(a)}
1354-
arr.rehash()
13551354
return arr
13561355
}
13571356

13581357
// Array represents an array as defined by the language. Arrays are similar to the
13591358
// same types as defined by JSON with the exception that they can contain Vars
13601359
// and References.
1360+
//
1361+
// Optimization: removed hashs []int slice to save memory (8*N bytes per array).
1362+
// Hash is now computed lazily on first access and cached in hashValid flag.
13611363
type Array struct {
1362-
elems []*Term
1363-
hashs []int // element hashes
1364-
hash int
1365-
ground bool
1364+
elems []*Term
1365+
hash int
1366+
ground bool
1367+
hashValid bool // true if hash is up-to-date
13661368
}
13671369

13681370
// Copy returns a deep copy of arr.
13691371
func (arr *Array) Copy() *Array {
13701372
return &Array{
1371-
elems: termSliceCopy(arr.elems),
1372-
hashs: slices.Clone(arr.hashs),
1373-
hash: arr.hash,
1374-
ground: arr.ground,
1373+
elems: termSliceCopy(arr.elems),
1374+
hash: arr.hash,
1375+
ground: arr.ground,
1376+
hashValid: arr.hashValid,
13751377
}
13761378
}
13771379

@@ -1460,12 +1462,24 @@ func (arr *Array) Sorted() *Array {
14601462
slices.SortFunc(cpy, TermValueCompare)
14611463

14621464
a := NewArray(cpy...)
1463-
a.hashs = arr.hashs
1465+
// If parent arr already has valid hash, copy it since sorting doesn't change hash
1466+
if arr.hashValid {
1467+
a.hash = arr.hash
1468+
a.hashValid = true
1469+
}
14641470
return a
14651471
}
14661472

14671473
// Hash returns the hash code for the Value.
1474+
// Computes hash lazily on first access and caches it.
14681475
func (arr *Array) Hash() int {
1476+
if !arr.hashValid {
1477+
arr.hash = 0
1478+
for _, e := range arr.elems {
1479+
arr.hash += e.Value.Hash()
1480+
}
1481+
arr.hashValid = true
1482+
}
14691483
return arr.hash
14701484
}
14711485

@@ -1515,20 +1529,16 @@ func (arr *Array) Set(i int, v *Term) {
15151529
arr.set(i, v)
15161530
}
15171531

1518-
// rehash updates the cached hash of arr.
1532+
// rehash invalidates the cached hash so it will be recomputed on next access.
15191533
func (arr *Array) rehash() {
1520-
arr.hash = 0
1521-
for _, h := range arr.hashs {
1522-
arr.hash += h
1523-
}
1534+
arr.hashValid = false
15241535
}
15251536

15261537
// set sets the element i of arr.
15271538
func (arr *Array) set(i int, v *Term) {
15281539
arr.ground = arr.ground && v.IsGround()
15291540
arr.elems[i] = v
1530-
arr.hashs[i] = v.Value.Hash()
1531-
arr.rehash()
1541+
arr.rehash() // invalidate hash cache
15321542
}
15331543

15341544
// Slice returns a slice of arr starting from i index to j. -1
@@ -1537,21 +1547,20 @@ func (arr *Array) set(i int, v *Term) {
15371547
// the other.
15381548
func (arr *Array) Slice(i, j int) *Array {
15391549
var elems []*Term
1540-
var hashs []int
15411550
if j == -1 {
15421551
elems = arr.elems[i:]
1543-
hashs = arr.hashs[i:]
15441552
} else {
15451553
elems = arr.elems[i:j]
1546-
hashs = arr.hashs[i:j]
15471554
}
15481555
// If arr is ground, the slice is, too.
15491556
// If it's not, the slice could still be.
15501557
gr := arr.ground || termSliceIsGround(elems)
15511558

1552-
s := &Array{elems: elems, hashs: hashs, ground: gr}
1553-
s.rehash()
1554-
return s
1559+
return &Array{
1560+
elems: elems,
1561+
ground: gr,
1562+
hashValid: false, // hash will be computed lazily
1563+
}
15551564
}
15561565

15571566
// Iter calls f on each element in arr. If f returns an error,
@@ -1581,9 +1590,14 @@ func (arr *Array) Foreach(f func(*Term)) {
15811590
func (arr *Array) Append(v *Term) *Array {
15821591
cpy := *arr
15831592
cpy.elems = append(arr.elems, v)
1584-
cpy.hashs = append(arr.hashs, v.Value.Hash())
1585-
cpy.hash = arr.hash + v.Value.Hash()
15861593
cpy.ground = arr.ground && v.IsGround()
1594+
// If hash was already computed, we can update it incrementally
1595+
if arr.hashValid {
1596+
cpy.hash = arr.hash + v.Value.Hash()
1597+
cpy.hashValid = true
1598+
} else {
1599+
cpy.hashValid = false // will be computed on first access
1600+
}
15871601
return &cpy
15881602
}
15891603

0 commit comments

Comments
 (0)