Skip to content

Commit 4768653

Browse files
committed
optimize: Enhance performance of various methods and add benchmarks
Signed-off-by: oleksandr.yershov <[email protected]>
1 parent 47e2b74 commit 4768653

File tree

4 files changed

+241
-34
lines changed

4 files changed

+241
-34
lines changed

v1/ast/compile.go

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -667,6 +667,9 @@ func (c *Compiler) GetRulesWithPrefix(ref Ref) (rules []*Rule) {
667667
}
668668

669669
func extractRules(s []any) []*Rule {
670+
if len(s) == 0 {
671+
return nil
672+
}
670673
rules := make([]*Rule, len(s))
671674
for i := range s {
672675
rules[i] = s[i].(*Rule)
@@ -691,7 +694,6 @@ func extractRules(s []any) []*Rule {
691694
// GetRules("data.a.b.c") => [rule1, rule2]
692695
// GetRules("data.a.b.d") => nil
693696
func (c *Compiler) GetRules(ref Ref) (rules []*Rule) {
694-
695697
set := map[*Rule]struct{}{}
696698

697699
for _, rule := range c.GetRulesForVirtualDocument(ref) {
@@ -702,10 +704,13 @@ func (c *Compiler) GetRules(ref Ref) (rules []*Rule) {
702704
set[rule] = struct{}{}
703705
}
704706

707+
if len(set) == 0 {
708+
return nil
709+
}
710+
rules = make([]*Rule, 0, len(set))
705711
for rule := range set {
706712
rules = append(rules, rule)
707713
}
708-
709714
return rules
710715
}
711716

v1/ast/optimization_bench_test.go

Lines changed: 129 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,129 @@
1+
// Copyright 2025 The OPA Authors. All rights reserved.
2+
// Use of this source code is governed by an Apache2
3+
// license that can be found in the LICENSE file.
4+
5+
package ast
6+
7+
import (
8+
"testing"
9+
)
10+
11+
// BenchmarkRefPtr benchmarks the optimized Ref.Ptr() method
12+
func BenchmarkRefPtr(b *testing.B) {
13+
ref := MustParseRef("data.foo.bar.baz.qux")
14+
15+
b.ResetTimer()
16+
for i := 0; i < b.N; i++ {
17+
_, _ = ref.Ptr()
18+
}
19+
}
20+
21+
// BenchmarkArgsString benchmarks the optimized Args.String() method
22+
func BenchmarkArgsString(b *testing.B) {
23+
args := Args{
24+
StringTerm("arg1"),
25+
StringTerm("arg2"),
26+
StringTerm("arg3"),
27+
NumberTerm("42"),
28+
}
29+
30+
b.ResetTimer()
31+
for i := 0; i < b.N; i++ {
32+
_ = args.String()
33+
}
34+
}
35+
36+
// BenchmarkBodyString benchmarks the optimized Body.String() method
37+
func BenchmarkBodyString(b *testing.B) {
38+
body := MustParseBody("x := 1; y := 2; z := x + y; a := z * 2")
39+
40+
b.ResetTimer()
41+
for i := 0; i < b.N; i++ {
42+
_ = body.String()
43+
}
44+
}
45+
46+
// BenchmarkExprString benchmarks the optimized Expr.String() method
47+
func BenchmarkExprString(b *testing.B) {
48+
expr := MustParseExpr("x = y + z with input.foo as 42 with data.bar as \"test\"")
49+
50+
b.ResetTimer()
51+
for i := 0; i < b.N; i++ {
52+
_ = expr.String()
53+
}
54+
}
55+
56+
// BenchmarkSetDiff benchmarks the optimized set.Diff() method
57+
func BenchmarkSetDiff(b *testing.B) {
58+
s1 := NewSet()
59+
s2 := NewSet()
60+
for i := 0; i < 100; i++ {
61+
s1.Add(IntNumberTerm(i))
62+
if i%2 == 0 {
63+
s2.Add(IntNumberTerm(i))
64+
}
65+
}
66+
67+
b.ResetTimer()
68+
for i := 0; i < b.N; i++ {
69+
_ = s1.Diff(s2)
70+
}
71+
}
72+
73+
// BenchmarkSetIntersect benchmarks the optimized set.Intersect() method
74+
func BenchmarkSetIntersect(b *testing.B) {
75+
s1 := NewSet()
76+
s2 := NewSet()
77+
for i := 0; i < 100; i++ {
78+
s1.Add(IntNumberTerm(i))
79+
if i%2 == 0 {
80+
s2.Add(IntNumberTerm(i))
81+
}
82+
}
83+
84+
b.ResetTimer()
85+
for i := 0; i < b.N; i++ {
86+
_ = s1.Intersect(s2)
87+
}
88+
}
89+
90+
// BenchmarkObjectKeys benchmarks the optimized object.Keys() method
91+
func BenchmarkObjectKeys(b *testing.B) {
92+
obj := NewObject()
93+
for i := 0; i < 50; i++ {
94+
obj.Insert(StringTerm(string(rune('a'+i%26))+string(rune('0'+i/26))), IntNumberTerm(i))
95+
}
96+
97+
b.ResetTimer()
98+
for i := 0; i < b.N; i++ {
99+
_ = obj.Keys()
100+
}
101+
}
102+
103+
// BenchmarkGetRules benchmarks the optimized Compiler.GetRules() method
104+
func BenchmarkGetRules(b *testing.B) {
105+
module := `
106+
package test
107+
108+
p[x] { x := 1 }
109+
p[x] { x := 2 }
110+
q[x] { x := 3 }
111+
r := 4
112+
`
113+
114+
c := NewCompiler()
115+
c.Compile(map[string]*Module{
116+
"test.rego": MustParseModule(module),
117+
})
118+
119+
if c.Failed() {
120+
b.Fatal(c.Errors)
121+
}
122+
123+
ref := MustParseRef("data.test.p")
124+
125+
b.ResetTimer()
126+
for i := 0; i < b.N; i++ {
127+
_ = c.GetRules(ref)
128+
}
129+
}

v1/ast/policy.go

Lines changed: 59 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1095,11 +1095,21 @@ func (a Args) Copy() Args {
10951095
}
10961096

10971097
func (a Args) String() string {
1098-
buf := make([]string, 0, len(a))
1099-
for _, t := range a {
1100-
buf = append(buf, t.String())
1098+
if len(a) == 0 {
1099+
return "()"
1100+
}
1101+
sb := sbPool.Get()
1102+
defer sbPool.Put(sb)
1103+
sb.Grow(len(a) * 10)
1104+
sb.WriteByte('(')
1105+
for i, t := range a {
1106+
if i > 0 {
1107+
sb.WriteString(", ")
1108+
}
1109+
sb.WriteString(t.String())
11011110
}
1102-
return "(" + strings.Join(buf, ", ") + ")"
1111+
sb.WriteByte(')')
1112+
return sb.String()
11031113
}
11041114

11051115
// Loc returns the Location of a.
@@ -1232,11 +1242,22 @@ func (body Body) SetLoc(loc *Location) {
12321242
}
12331243

12341244
func (body Body) String() string {
1235-
buf := make([]string, 0, len(body))
1236-
for _, v := range body {
1237-
buf = append(buf, v.String())
1245+
if len(body) == 0 {
1246+
return ""
1247+
}
1248+
if len(body) == 1 {
1249+
return body[0].String()
1250+
}
1251+
sb := sbPool.Get()
1252+
defer sbPool.Put(sb)
1253+
sb.Grow(len(body) * 20)
1254+
for i, v := range body {
1255+
if i > 0 {
1256+
sb.WriteString("; ")
1257+
}
1258+
sb.WriteString(v.String())
12381259
}
1239-
return strings.Join(buf, "; ")
1260+
return sb.String()
12401261
}
12411262

12421263
// Vars returns a VarSet containing variables in body. The params can be set to
@@ -1547,26 +1568,34 @@ func (expr *Expr) SetLoc(loc *Location) {
15471568
}
15481569

15491570
func (expr *Expr) String() string {
1550-
buf := make([]string, 0, 2+len(expr.With))
1571+
sb := sbPool.Get()
1572+
defer sbPool.Put(sb)
1573+
sb.Grow(32 + len(expr.With)*20)
1574+
15511575
if expr.Negated {
1552-
buf = append(buf, "not")
1576+
sb.WriteString("not ")
15531577
}
15541578
switch t := expr.Terms.(type) {
15551579
case []*Term:
15561580
if expr.IsEquality() && validEqAssignArgCount(expr) {
1557-
buf = append(buf, fmt.Sprintf("%v %v %v", t[1], Equality.Infix, t[2]))
1581+
sb.WriteString(t[1].String())
1582+
sb.WriteByte(' ')
1583+
sb.WriteString(Equality.Infix)
1584+
sb.WriteByte(' ')
1585+
sb.WriteString(t[2].String())
15581586
} else {
1559-
buf = append(buf, Call(t).String())
1587+
sb.WriteString(Call(t).String())
15601588
}
15611589
case fmt.Stringer:
1562-
buf = append(buf, t.String())
1590+
sb.WriteString(t.String())
15631591
}
15641592

15651593
for i := range expr.With {
1566-
buf = append(buf, expr.With[i].String())
1594+
sb.WriteByte(' ')
1595+
sb.WriteString(expr.With[i].String())
15671596
}
15681597

1569-
return strings.Join(buf, " ")
1598+
return sb.String()
15701599
}
15711600

15721601
func (expr *Expr) MarshalJSON() ([]byte, error) {
@@ -1666,11 +1695,22 @@ func (d *SomeDecl) String() string {
16661695
}
16671696
return "some " + call[1].String() + " in " + call[2].String()
16681697
}
1669-
buf := make([]string, len(d.Symbols))
1670-
for i := range buf {
1671-
buf[i] = d.Symbols[i].String()
1698+
if len(d.Symbols) == 0 {
1699+
return "some"
1700+
}
1701+
if len(d.Symbols) == 1 {
1702+
return "some " + d.Symbols[0].String()
1703+
}
1704+
sb := sbPool.Get()
1705+
defer sbPool.Put(sb)
1706+
sb.WriteString("some ")
1707+
for i := range d.Symbols {
1708+
if i > 0 {
1709+
sb.WriteString(", ")
1710+
}
1711+
sb.WriteString(d.Symbols[i].String())
16721712
}
1673-
return "some " + strings.Join(buf, ", ")
1713+
return sb.String()
16741714
}
16751715

16761716
// SetLoc sets the Location on d.

0 commit comments

Comments
 (0)