Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

More reduced allocations #7193

Open
wants to merge 1 commit into
base: main
Choose a base branch
from

Conversation

anderseknert
Copy link
Member

For the first time, down under a 100 million allocations when running regal lint bundle 🎈

main

BenchmarkLintAllEnabled-10    1	2538350916 ns/op	6182626816 B/op	108424249 allocs/op

pr

BenchmarkLintAllEnabled-10    1	2297548750 ns/op	5355014152 B/op	94603784 allocs/op

But there's more to it than just the number of allocations:

➜ hyperfine -i --warmup 1 'regal lint bundle' 'regal-new lint bundle'
Benchmark 1: regal lint bundle
  Time (mean ± σ):      2.822 s ±  0.055 s    [User: 19.299 s, System: 0.603 s]
  Range (min … max):    2.743 s …  2.961 s    10 runs

Benchmark 2: regal-new lint bundle
  Time (mean ± σ):      2.373 s ±  0.040 s    [User: 15.940 s, System: 0.575 s]
  Range (min … max):    2.315 s …  2.435 s    10 runs

Summary
  regal-new lint bundle ran
    1.19 ± 0.03 times faster than regal lint bundle

Most notable changes:

  • Reuse trieTraversalResult in indexing, as these were expensive and short-lived. This had the most dramatic impact on the number of reduced allocations of all the changes here.

  • Optimize *set, *object and *Array operations to minimize allocations by using "primitive" form iteration instead of the function literal counterparts internally, and to only reset the sort guard when needed.

  • New Array.Equal implementation does not remove any allocations as the old implementation didn't allocate either. It did however perform much better for the case where the compared arrays were not equal.

Copy link
Contributor

@srenatus srenatus left a comment

Choose a reason for hiding this comment

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

Thanks for working on this. Some comments inline 🙃

s.Foreach(func(x *Term) {
if !other.Contains(x) {
r.Add(x)
terms := make([]*Term, 0, len(s.keys))
Copy link
Contributor

Choose a reason for hiding this comment

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

Overallocating is better than re-allocating, I suppose? 🤔

Copy link
Member Author

Choose a reason for hiding this comment

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

Not sure tbh, an the length can probably be tweaked. Rewriting these was not a huge allocation saver, but it did save some and performed consistently better across all tests, so I left it be.

topdown/walk.go Outdated
if filter == nil || filter.Len() == 0 {
if path == nil {
path = ast.NewArray()
}

if err := iter(ast.ArrayTerm(ast.NewTerm(path.Copy()), input)); err != nil {
// TODO: why does this not work?
Copy link
Contributor

Choose a reason for hiding this comment

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

TODO: let's find an answer before merging this 😉

I think it doesn't work because defer doesn't go well with CPS. return iter(...) will mean that the defer runs when everything is evaluated successfully.

Copy link
Member Author

Choose a reason for hiding this comment

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

let's find an answer before merging this

Naturally :) I'll see if I can get something to work without defer ... but note that this actually does work in the walkNoPath version... the few added unit tests on this confirms so, but even more so that regal lintworks, as we're quite dependent on that.

Copy link
Member Author

Choose a reason for hiding this comment

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

I think it doesn't work because defer doesn't go well with CPS. return iter(...) will mean that the defer runs when everything is evaluated successfully.

This seems to align with what some println's tell me. However, I'm not sure why this would be an issue. Even if all path/value pairs aren't returned to the pool until after the whole walk is done, that would still be a substantial improvement over having to create all new items the next walk is invoked. BUT the YAML tests show some really odd behavior in the printed path/values vs the expected ones! But now that I try to run these changes using regal lint bundle, everything actually works.. and we use walk (even with path) quite extensively... so I'm really really clueless as to what's going on right now 😅

Copy link
Member Author

Choose a reason for hiding this comment

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

@srenatus I have now pushed my latest changes, that return items to the pool "manually" instead of via defer. I also added a pool for terms as they too can be reduced, and all in all this is another 4 million allocations down if successful. The OPA YAML tests related to walk are failing, where Regal just keeps going. I don't even know where to start 😄 But perhaps you'll come up with some ideas. I figured I'd push this so that we're on the same page.

topdown/walk.go Outdated Show resolved Hide resolved
@anderseknert anderseknert force-pushed the more-interning branch 2 times, most recently from d6102d8 to 409425a Compare November 26, 2024 20:05
@anderseknert
Copy link
Member Author

The failing test is "unrelated to this PR", which is a bit of a bold statement, given that the test was added by this very PR. But that test fails even on current OPA, and it seems like we have some ordering issue in the Wasm-implementation of walk.

 --- FAIL: TestWasmE2E (308.07s)
    --- FAIL: TestWasmE2E/test/cases/testdata/v1/walkbuiltin/test-walkbuiltin-0972.yaml/walkbuiltin/objects_no_path (0.09s)
        external_test.go:110: expected {{"x": [{"v1": "hello", "v2": "goodbye"}, "hello", "goodbye"]}} but got {{"x": [{"v1": "hello", "v2": "goodbye"}, "goodbye", "hello"]}}


termPool = sync.Pool{
New: func() any {
return ast.NewTerm(ast.Boolean(false))
Copy link
Contributor

Choose a reason for hiding this comment

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

I think I'd be less surprised about something like return &ast.Term{} -- why bother with a Value if it's not supposed to have one in the first place?

@anderseknert anderseknert force-pushed the more-interning branch 2 times, most recently from 2a7b437 to 49d325c Compare November 30, 2024 20:49
For the first time, down  under a 100 million allocations
when running `regal lint bundle` 🎈

**main**
```
BenchmarkLintAllEnabled-10    1	2538350916 ns/op	6182626816 B/op	108424249 allocs/op
```

**pr**
```
BenchmarkLintAllEnabled-10    1	2282894416 ns/op	5310032744 B/op	93674054 allocs/op
```

But there's more to it than just the number of allocations:

```
➜ hyperfine -i --warmup 1 'regal lint bundle' 'regal-new lint bundle'
Benchmark 1: regal lint bundle
  Time (mean ± σ):      2.822 s ±  0.055 s    [User: 19.299 s, System: 0.603 s]
  Range (min … max):    2.743 s …  2.961 s    10 runs

Benchmark 2: regal-new lint bundle
  Time (mean ± σ):      2.373 s ±  0.040 s    [User: 15.940 s, System: 0.575 s]
  Range (min … max):    2.315 s …  2.435 s    10 runs

Summary
  regal-new lint bundle ran
    1.19 ± 0.03 times faster than regal lint bundle
```

Most notable changes:

- Reuse trieTraversalResult in indexing, as these were expensive
  and short-lived. This had the most dramatic impact on the number
  of reduced allocations of all the changes here.

- Optimize *set, *object and *Array operations to minimize
  allocations by using "primitive" form iteration instead of
  the function literal counterparts internally, and to only
  reset the sort guard when needed.

- New Array.Equal implementation does not remove any allocations
  as the old implementation didn't allocate either. It did however
  perform much better for the case where the compared arrays were
  not equal.

Signed-off-by: Anders Eknert <[email protected]>
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