Skip to content

Commit

Permalink
Optimize biunify for arrays
Browse files Browse the repository at this point in the history
The construction of intermediate throwaway `ast.Array`s caused almost
5 million allocations when running `regal lint` against its own bundle.

While the results are likely more impressive for Regal policies than the
average one, this is still a substantial improvement across a large group
of policies.

**regal lint OPA main**
```
BenchmarkRegalLintingItself-10    1	3317838417 ns/op	6611412800 B/op	124873123 allocs/op
```

**regal lint this branch**
```
BenchmarkRegalLintingItself-10    1	3140384416 ns/op	6590159176 B/op	120098613 allocs/op
```

Signed-off-by: Anders Eknert <[email protected]>
  • Loading branch information
anderseknert committed Nov 11, 2024
1 parent e507c41 commit b806265
Show file tree
Hide file tree
Showing 2 changed files with 14 additions and 7 deletions.
6 changes: 6 additions & 0 deletions ast/term.go
Original file line number Diff line number Diff line change
Expand Up @@ -1240,6 +1240,12 @@ func (arr *Array) Get(pos *Term) *Term {
return nil
}

// ArrayElems returns the term slice of the array. This should
// normally not be used, and is exposed for internal purposes only.
func ArrayElems(a *Array) []*Term {
return a.elems
}

// Sorted returns a new Array that contains the sorted elements of arr.
func (arr *Array) Sorted() *Array {
cpy := make([]*Term, len(arr.elems))
Expand Down
15 changes: 8 additions & 7 deletions topdown/eval.go
Original file line number Diff line number Diff line change
Expand Up @@ -901,7 +901,7 @@ func (e *eval) biunify(a, b *ast.Term, b1, b2 *bindings, iter unifyIterator) err
case ast.Var, ast.Ref, *ast.ArrayComprehension:
return e.biunifyValues(a, b, b1, b2, iter)
case *ast.Array:
return e.biunifyArrays(vA, vB, b1, b2, iter)
return e.biunifyArrays(ast.ArrayElems(vA), ast.ArrayElems(vB), b1, b2, iter)
}
case ast.Object:
switch vB := b.Value.(type) {
Expand All @@ -916,18 +916,19 @@ func (e *eval) biunify(a, b *ast.Term, b1, b2 *bindings, iter unifyIterator) err
return nil
}

func (e *eval) biunifyArrays(a, b *ast.Array, b1, b2 *bindings, iter unifyIterator) error {
if a.Len() != b.Len() {
func (e *eval) biunifyArrays(a, b []*ast.Term, b1, b2 *bindings, iter unifyIterator) error {
if len(a) != len(b) {
return nil
}

return e.biunifyArraysRec(a, b, b1, b2, iter, 0)
}

func (e *eval) biunifyArraysRec(a, b *ast.Array, b1, b2 *bindings, iter unifyIterator, idx int) error {
if idx == a.Len() {
func (e *eval) biunifyArraysRec(a, b []*ast.Term, b1, b2 *bindings, iter unifyIterator, idx int) error {
if idx == len(a) {
return iter()
}
return e.biunify(a.Elem(idx), b.Elem(idx), b1, b2, func() error {
return e.biunify(a[idx], b[idx], b1, b2, func() error {
return e.biunifyArraysRec(a, b, b1, b2, iter, idx+1)
})
}
Expand Down Expand Up @@ -1962,7 +1963,7 @@ func (e evalFunc) evalOneRule(iter unifyIterator, rule *ast.Rule, cacheKey ast.R

child.traceEnter(rule)

err := child.biunifyArrays(ast.NewArray(e.terms[1:]...), ast.NewArray(args...), e.e.bindings, child.bindings, func() error {
err := child.biunifyArrays(e.terms[1:], args, e.e.bindings, child.bindings, func() error {
return child.eval(func(child *eval) error {
child.traceExit(rule)

Expand Down

0 comments on commit b806265

Please sign in to comment.