Skip to content

perf: AST compiler optimizations#7740

Merged
anderseknert merged 1 commit intoopen-policy-agent:mainfrom
anderseknert:compiler-optimizations
Jul 25, 2025
Merged

perf: AST compiler optimizations#7740
anderseknert merged 1 commit intoopen-policy-agent:mainfrom
anderseknert:compiler-optimizations

Conversation

@anderseknert
Copy link
Copy Markdown
Member

@anderseknert anderseknert commented Jun 30, 2025

Funnily, this started out as an attempt to look into issues reported with compiling large policy sets... before I realized that it isn't likely this compiler that has perf issues, but the one that "compiles" bundles as part of activation. So while these fixes likely does little to address that, there are still some rather nice improvements here, where the big ones as ususal are mostly just wins from avoiding work where it's possible.

For benchmarking I've used Regal's embedded bundle, which isn't great to use over time, as it's a moving target. But since it's a pretty extensive bundle and one that covers most features of OPA, it's at least good for 1:1 comparisons when testing perf improvements.

// 66555594 ns/op	50239492 B/op	 1083664 allocs/op - main
// 62569440 ns/op	38723015 B/op	  944277 allocs/op - compiler-optimizations pr

The B/op / alloc_space improvement is particularly nice here. What's noteworthy is how relatively little impact that has on ns/op in this case. That may be surprising but aligns pretty well with my previous experience of Go code where a lot of time is spend in recursive walks — that simply takes time, no matter how much you optimize. Oh well, less memory allocated for this is more memory to spend elsewhere.

(I'm adding the benchmark used below to Regal in a parallel PR)

@anderseknert anderseknert marked this pull request as ready for review June 30, 2025 20:24
@netlify
Copy link
Copy Markdown

netlify Bot commented Jun 30, 2025

Deploy Preview for openpolicyagent ready!

Name Link
🔨 Latest commit a4f34ae
🔍 Latest deploy log https://app.netlify.com/projects/openpolicyagent/deploys/68836cd5c4e691000897a36d
😎 Deploy Preview https://deploy-preview-7740--openpolicyagent.netlify.app
📱 Preview on mobile
Toggle QR Code...

QR Code

Use your smartphone camera to open QR code link.

To edit notification comments on pull requests, go to your Netlify project configuration.

anderseknert added a commit to open-policy-agent/regal that referenced this pull request Jun 30, 2025
This is really testing OPA rather than Regal, but since it's
a good test case for improving OPA for Regal, it belongs here.

Used in open-policy-agent/opa#7740

Signed-off-by: Anders Eknert <anders@styra.com>
anderseknert added a commit to open-policy-agent/regal that referenced this pull request Jun 30, 2025
This is really testing OPA rather than Regal, but since it's
a good test case for improving OPA for Regal, it belongs here.

Used in open-policy-agent/opa#7740

Signed-off-by: Anders Eknert <anders@styra.com>
anderseknert added a commit to open-policy-agent/regal that referenced this pull request Jul 1, 2025
This is really testing OPA rather than Regal, but since it's
a good test case for improving OPA for Regal, it belongs here.

Used in open-policy-agent/opa#7740

Signed-off-by: Anders Eknert <anders@styra.com>
Copy link
Copy Markdown
Contributor

@johanfylling johanfylling left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just some initial comments. Will wrap the complete review later.

Comment thread cmd/eval.go
Comment thread v1/ast/compile.go
Comment thread v1/ast/compile.go
Comment thread v1/ast/compile.go
@anderseknert anderseknert force-pushed the compiler-optimizations branch from 299b3ef to be93165 Compare July 22, 2025 14:11
Copy link
Copy Markdown
Contributor

@johanfylling johanfylling left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks!

My only concern is the change in evalNot().

Comment thread v1/ast/compile.go
return errs
}

reversed := make(map[Var]Var, len(dvs.vs))
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks like this is only used if we need to report an error. And isn't used in a successful compilation. Could be done lazily?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The code above should ensure it's only created when there are actual unused vars, which is the real improvement from before:

if len(unused) == 0 {
		return errs
}

Over-allocating is almost always better than allocating multiple times, like when a maps has to grow. It's true that this could be made even more efficient though if we did some of the logic below here first to count and then to allocate.

Comment thread v1/ast/compile.go

unused := dbv.Diff(used)

reversed := make(map[Var]Var, len(dvs.vs))
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Before this change, we built the reverse map as we added entries. This is cheaper?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Indeed, as previously we would pre-allocate the map on the dvs struct, and now we don't allocate anything in most cases.

Comment thread v1/topdown/eval.go Outdated
child.traceExit(negation)
child.traceRedo(negation)
if err := child.eval(func(c *eval) error {
if c.traceEnabled {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Are we sure c is the same instance as child here? E.g. what if evaluation spawns additional children?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fair enough. reverted

Funnily, this started out as an attempt to look into issues reported
with compiling large policy sets... before I realized that it isn't
likely *this* compiler that has perf issues, but the one that "compiles"
bundles as part of activation. So while these fixes likely does little
to address that, there are still some rather nice improvements here, where
the big ones as ususal are mostly just wins from avoiding work where it's
possible.

For benchmarking I've used Regal's embedded bundle, which isn't great to
use over time, as it's a moving target. But since it's a pretty extensive
bundle and one that covers most features of OPA, it's at least good for
1:1 comparisons when testing perf improvements.

```
// 66555594 ns/op	50239492 B/op	 1083664 allocs/op - main
// 62569440 ns/op	38723015 B/op	  944277 allocs/op - compiler-optimizations pr
```
The B/op / alloc_space improvement is particularly nice here. What's noteworthy
is how relatively little impact that has on performance in this case. That may
be surprising but aligns pretty well with my previous experience of Go code where
a lot of time is spend in recursive walks — that simply takes time, no matter how
much you optimize. Oh well, less memory allocated for this is more memory to spend
elsewhere.

(I'm adding the benchmark used below to Regal in a parallel PR)

Signed-off-by: Anders Eknert <anders@styra.com>
@anderseknert anderseknert force-pushed the compiler-optimizations branch from be93165 to a4f34ae Compare July 25, 2025 11:38
Copy link
Copy Markdown
Contributor

@johanfylling johanfylling left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍

@anderseknert anderseknert merged commit 4c13c6c into open-policy-agent:main Jul 25, 2025
31 checks passed
@anderseknert anderseknert deleted the compiler-optimizations branch July 27, 2025 19:43
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants