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

Improve plan summary performance #2907

Merged

Conversation

inkel
Copy link
Contributor

@inkel inkel commented Jan 2, 2023

what

At Grafana Labs we have been using Atlantis for several months, and a typical PR could affect over 10 to 20 different projects; these PRs happen several times a day, meaning Atlantis reports back plan summaries very often.

While looking at how to override the templates to better accommodate them to our needs, I've found that the *PlanSuccess.Summary method could be made much faster by simply extracting the compilation of the regexes outside the method instead of compiling them every time.

why

As described above we rely heavily on Atlantis giving us faster feedback. By improving the performance in speed and memory allocations the feedback cycle will be faster and also we will be reducing our carbon footprint.

tests

I first introduced a benchmark for this method, and ran the tests and benchmark locally to take a baseline:

$ go test -bench=. ./server/events/command
goos: darwin
goarch: arm64
pkg: github.com/runatlantis/atlantis/server/events/command
BenchmarkPlanSuccess_Summary/empty_summary,_no_matches-10                  82564             12415 ns/op           27352 B/op        114 allocs/op
BenchmarkPlanSuccess_Summary/changes-10                                   167904              7148 ns/op           14309 B/op         69 allocs/op
BenchmarkPlanSuccess_Summary/no_changes-10                                 92410             12984 ns/op           27705 B/op        114 allocs/op
BenchmarkPlanSuccess_Summary/changes_outside_Terraform-10                  89256             13505 ns/op           27882 B/op        117 allocs/op
BenchmarkPlanSuccess_Summary/changes_and_changes_outside-10               159199              7527 ns/op           14493 B/op         72 allocs/op
PASS
ok      github.com/runatlantis/atlantis/server/events/command   7.536s

After introducing my changes, I've ran the benchmarks and the tests again, and got this:

$ go test -bench=. ./server/events/command 
goos: darwin
goarch: arm64
pkg: github.com/runatlantis/atlantis/server/events/command
BenchmarkPlanSuccess_Summary/changes_outside_Terraform-10                1412385               820.7 ns/op           193 B/op          3 allocs/op
BenchmarkPlanSuccess_Summary/changes_and_changes_outside-10              1587134               757.8 ns/op           177 B/op          3 allocs/op
BenchmarkPlanSuccess_Summary/empty_summary,_no_matches-10               90337636                13.15 ns/op            0 B/op          0 allocs/op
BenchmarkPlanSuccess_Summary/changes-10                                  3303454               363.9 ns/op             0 B/op          0 allocs/op
BenchmarkPlanSuccess_Summary/no_changes-10                               3318954               361.5 ns/op             0 B/op          0 allocs/op
PASS
ok      github.com/runatlantis/atlantis/server/events/command   9.102s

To have a better comparison I've ran all benchmarks with 200,000 iterations, 10 times each, and got the following result using benchstat:

$ benchstat orig.txt improv.txt
name                                                old time/op    new time/op    delta
PlanSuccess_Summary/empty_summary,_no_matches-10      12.6µs ± 2%     0.0µs ± 1%   -99.90%  (p=0.000 n=10+9)
PlanSuccess_Summary/changes-10                        7.19µs ± 0%    0.36µs ± 2%   -94.96%  (p=0.000 n=8+9)
PlanSuccess_Summary/no_changes-10                     13.2µs ± 2%     0.4µs ± 0%   -97.27%  (p=0.000 n=10+9)
PlanSuccess_Summary/changes_outside_Terraform-10      13.7µs ± 1%     0.8µs ± 0%   -94.02%  (p=0.000 n=9+9)
PlanSuccess_Summary/changes_and_changes_outside-10    7.71µs ± 5%    0.75µs ± 0%   -90.21%  (p=0.000 n=10+8)

name                                                old alloc/op   new alloc/op   delta
PlanSuccess_Summary/empty_summary,_no_matches-10      27.4kB ± 0%     0.0kB       -100.00%  (p=0.000 n=10+10)
PlanSuccess_Summary/changes-10                        14.3kB ± 0%     0.0kB       -100.00%  (p=0.000 n=9+10)
PlanSuccess_Summary/no_changes-10                     27.7kB ± 0%     0.0kB       -100.00%  (p=0.000 n=10+10)
PlanSuccess_Summary/changes_outside_Terraform-10      27.9kB ± 0%     0.2kB ± 0%   -99.31%  (p=0.000 n=10+7)
PlanSuccess_Summary/changes_and_changes_outside-10    14.5kB ± 0%     0.2kB ± 0%   -98.78%  (p=0.000 n=10+8)

name                                                old allocs/op  new allocs/op  delta
PlanSuccess_Summary/empty_summary,_no_matches-10         114 ± 0%         0       -100.00%  (p=0.000 n=10+10)
PlanSuccess_Summary/changes-10                          69.0 ± 0%       0.0       -100.00%  (p=0.000 n=10+10)
PlanSuccess_Summary/no_changes-10                        114 ± 0%         0       -100.00%  (p=0.000 n=10+10)
PlanSuccess_Summary/changes_outside_Terraform-10         117 ± 0%         3 ± 0%   -97.44%  (p=0.000 n=10+10)
PlanSuccess_Summary/changes_and_changes_outside-10      72.0 ± 0%       3.0 ± 0%   -95.83%  (p=0.000 n=10+10)

references

inkel added 2 commits January 2, 2023 15:56
This is for taking a baseline and comparing with future
improvements. I just did a test run and got the following results:

$ go test -bench=. ./server/events/command
goos: darwin
goarch: arm64
pkg: github.com/runatlantis/atlantis/server/events/command
BenchmarkPlanSuccess_Summary/empty_summary,_no_matches-10                  82564             12415 ns/op           27352 B/op        114 allocs/op
BenchmarkPlanSuccess_Summary/changes-10                                   167904              7148 ns/op           14309 B/op         69 allocs/op
BenchmarkPlanSuccess_Summary/no_changes-10                                 92410             12984 ns/op           27705 B/op        114 allocs/op
BenchmarkPlanSuccess_Summary/changes_outside_Terraform-10                  89256             13505 ns/op           27882 B/op        117 allocs/op
BenchmarkPlanSuccess_Summary/changes_and_changes_outside-10               159199              7527 ns/op           14493 B/op         72 allocs/op
PASS
ok      github.com/runatlantis/atlantis/server/events/command   7.536s

To have a better comparison I've ran all the benchmarks with 200,000
iterations, and got this:

$ go test -bench=. ./server/events/command -benchtime=200000x
goos: darwin
goarch: arm64
pkg: github.com/runatlantis/atlantis/server/events/command
BenchmarkPlanSuccess_Summary/changes_and_changes_outside-10               200000              7637 ns/op           14487 B/op         72 allocs/op
BenchmarkPlanSuccess_Summary/empty_summary,_no_matches-10                 200000             12379 ns/op           27352 B/op        114 allocs/op
BenchmarkPlanSuccess_Summary/changes-10                                   200000              7134 ns/op           14312 B/op         69 allocs/op
BenchmarkPlanSuccess_Summary/no_changes-10                                200000             12960 ns/op           27692 B/op        114 allocs/op
BenchmarkPlanSuccess_Summary/changes_outside_Terraform-10                 200000             13731 ns/op           27862 B/op        117 allocs/op
PASS
ok      github.com/runatlantis/atlantis/server/events/command   11.837s

Signed-off-by: Leandro López (inkel) <[email protected]>
By initializing these regexes at program first run instead of every
time the function is called we gain better resource usage and much
faster results, as shown in this results with 200,000 iterations as in
the previous commit:

$ go test -bench=. ./server/events/command -benchtime=200000x
goos: darwin
goarch: arm64
pkg: github.com/runatlantis/atlantis/server/events/command
BenchmarkPlanSuccess_Summary/empty_summary,_no_matches-10                 200000                29.49 ns/op            0 B/op          0 allocs/op
BenchmarkPlanSuccess_Summary/changes-10                                   200000               450.4 ns/op             0 B/op          0 allocs/op
BenchmarkPlanSuccess_Summary/no_changes-10                                200000               357.9 ns/op             0 B/op          0 allocs/op
BenchmarkPlanSuccess_Summary/changes_outside_Terraform-10                 200000               824.1 ns/op           193 B/op          3 allocs/op
BenchmarkPlanSuccess_Summary/changes_and_changes_outside-10               200000               754.8 ns/op           177 B/op          3 allocs/op
PASS
ok      github.com/runatlantis/atlantis/server/events/command   0.943s

With 200,000 iterations the results were almost immediately and I
couldn't observe the real performance, so I've removed the limitation
and got the following:

$ go test -bench=. ./server/events/command -benchtime=200000x
goos: darwin
goarch: arm64
pkg: github.com/runatlantis/atlantis/server/events/command
BenchmarkPlanSuccess_Summary/empty_summary,_no_matches-10                 200000                29.49 ns/op            0 B/op          0 allocs/op
BenchmarkPlanSuccess_Summary/changes-10                                   200000               450.4 ns/op             0 B/op          0 allocs/op
BenchmarkPlanSuccess_Summary/no_changes-10                                200000               357.9 ns/op             0 B/op          0 allocs/op
BenchmarkPlanSuccess_Summary/changes_outside_Terraform-10                 200000               824.1 ns/op           193 B/op          3 allocs/op
BenchmarkPlanSuccess_Summary/changes_and_changes_outside-10               200000               754.8 ns/op           177 B/op          3 allocs/op
PASS
ok      github.com/runatlantis/atlantis/server/events/command   0.943s

I've also compared the results of running the benchamrks with 200,000
iterations, 10 times each, and these are the results:

$ benchstat orig.txt improv.txt
name                                                old time/op    new time/op    delta
PlanSuccess_Summary/empty_summary,_no_matches-10      12.6µs ± 2%     0.0µs ± 1%   -99.90%  (p=0.000 n=10+9)
PlanSuccess_Summary/changes-10                        7.19µs ± 0%    0.36µs ± 2%   -94.96%  (p=0.000 n=8+9)
PlanSuccess_Summary/no_changes-10                     13.2µs ± 2%     0.4µs ± 0%   -97.27%  (p=0.000 n=10+9)
PlanSuccess_Summary/changes_outside_Terraform-10      13.7µs ± 1%     0.8µs ± 0%   -94.02%  (p=0.000 n=9+9)
PlanSuccess_Summary/changes_and_changes_outside-10    7.71µs ± 5%    0.75µs ± 0%   -90.21%  (p=0.000 n=10+8)

name                                                old alloc/op   new alloc/op   delta
PlanSuccess_Summary/empty_summary,_no_matches-10      27.4kB ± 0%     0.0kB       -100.00%  (p=0.000 n=10+10)
PlanSuccess_Summary/changes-10                        14.3kB ± 0%     0.0kB       -100.00%  (p=0.000 n=9+10)
PlanSuccess_Summary/no_changes-10                     27.7kB ± 0%     0.0kB       -100.00%  (p=0.000 n=10+10)
PlanSuccess_Summary/changes_outside_Terraform-10      27.9kB ± 0%     0.2kB ± 0%   -99.31%  (p=0.000 n=10+7)
PlanSuccess_Summary/changes_and_changes_outside-10    14.5kB ± 0%     0.2kB ± 0%   -98.78%  (p=0.000 n=10+8)

name                                                old allocs/op  new allocs/op  delta
PlanSuccess_Summary/empty_summary,_no_matches-10         114 ± 0%         0       -100.00%  (p=0.000 n=10+10)
PlanSuccess_Summary/changes-10                          69.0 ± 0%       0.0       -100.00%  (p=0.000 n=10+10)
PlanSuccess_Summary/no_changes-10                        114 ± 0%         0       -100.00%  (p=0.000 n=10+10)
PlanSuccess_Summary/changes_outside_Terraform-10         117 ± 0%         3 ± 0%   -97.44%  (p=0.000 n=10+10)
PlanSuccess_Summary/changes_and_changes_outside-10      72.0 ± 0%       3.0 ± 0%   -95.83%  (p=0.000 n=10+10)

Signed-off-by: Leandro López (inkel) <[email protected]>
@inkel inkel requested a review from a team as a code owner January 2, 2023 19:27
@inkel
Copy link
Contributor Author

inkel commented Jan 2, 2023

I've noted that PlanSuccess.DiffMarkdownFormattedTerraformOutput also could be improved the same way, however, that one doesn't seem to have a specific test for it, so I haven't changed it yet.

There's no need for fmt.Sprintf for such a simple use.

Signed-off-by: Leandro López (inkel) <[email protected]>
@jamengual jamengual added the quick-win Is obviously something Atlantis should do and will take <4 hrs work label Jan 3, 2023
Copy link
Member

@GenPage GenPage left a comment

Choose a reason for hiding this comment

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

This is an excellent change and much appreciated with the benchmark testing. 💯

@inkel
Copy link
Contributor Author

inkel commented Jan 4, 2023

Thanks for the nice words @GenPage!

@jamengual
Copy link
Contributor

jamengual commented Jan 4, 2023

@inkel do you think you could add the improvements you mention in PlanSuccess.DiffMarkdownFormattedTerraformOutput in this PR?

and thanks a lot for this PR

@inkel
Copy link
Contributor Author

inkel commented Jan 4, 2023

@jamengual I sure can! Note that as I said above there are no tests for that specific method, but from what I can gather by reading the code it should be an easy to follow change and nothing should break.

@inkel
Copy link
Contributor Author

inkel commented Jan 4, 2023

I've found a test for it, it was in the MarkdownRenderer test suite. I'm going to add a benchmark there and the apply the improvements.

inkel added 2 commits January 4, 2023 15:24
This change doesn't improve performance as dramatically as the summary
regexes do, however, it does reduce the amount of resources used:

$ benchstat markdowndiff.orig.txt markdowndiff.inkel.txt
name                                                                                                                   old time/op    new time/op    delta
RenderProjectResultsWithEnableDiffMarkdownFormat/single_successful_plan_with_diff_markdown_formatted/verbose_true-10      727µs ± 0%     717µs ± 2%   -1.31%  (p=0.002 n=10+10)
RenderProjectResultsWithEnableDiffMarkdownFormat/single_successful_plan_with_diff_markdown_formatted/verbose_false-10     725µs ± 0%     715µs ± 1%   -1.33%  (p=0.000 n=9+10)

name                                                                                                                   old alloc/op   new alloc/op   delta
RenderProjectResultsWithEnableDiffMarkdownFormat/single_successful_plan_with_diff_markdown_formatted/verbose_true-10      171kB ± 0%     153kB ± 0%  -10.60%  (p=0.000 n=10+10)
RenderProjectResultsWithEnableDiffMarkdownFormat/single_successful_plan_with_diff_markdown_formatted/verbose_false-10     171kB ± 0%     153kB ± 0%  -10.60%  (p=0.000 n=10+10)

name                                                                                                                   old allocs/op  new allocs/op  delta
RenderProjectResultsWithEnableDiffMarkdownFormat/single_successful_plan_with_diff_markdown_formatted/verbose_true-10        364 ± 0%       214 ± 0%  -41.29%  (p=0.000 n=10+9)
RenderProjectResultsWithEnableDiffMarkdownFormat/single_successful_plan_with_diff_markdown_formatted/verbose_false-10       358 ± 0%       207 ± 0%  -42.05%  (p=0.000 n=10+10)

Signed-off-by: Leandro López (inkel) <[email protected]>
@inkel
Copy link
Contributor Author

inkel commented Jan 4, 2023

@jamengual done. Doing the same changes doesn't reduce the performance as dramatically as we've observed in the summary function, however it does reduce the allocations and memory used:

$ benchstat markdowndiff.orig.txt markdowndiff.inkel.txt 
name                                                                                                                   old time/op    new time/op    delta
RenderProjectResultsWithEnableDiffMarkdownFormat/single_successful_plan_with_diff_markdown_formatted/verbose_true-10      727µs ± 0%     717µs ± 2%   -1.31%  (p=0.002 n=10+10)
RenderProjectResultsWithEnableDiffMarkdownFormat/single_successful_plan_with_diff_markdown_formatted/verbose_false-10     725µs ± 0%     715µs ± 1%   -1.33%  (p=0.000 n=9+10)

name                                                                                                                   old alloc/op   new alloc/op   delta
RenderProjectResultsWithEnableDiffMarkdownFormat/single_successful_plan_with_diff_markdown_formatted/verbose_true-10      171kB ± 0%     153kB ± 0%  -10.60%  (p=0.000 n=10+10)
RenderProjectResultsWithEnableDiffMarkdownFormat/single_successful_plan_with_diff_markdown_formatted/verbose_false-10     171kB ± 0%     153kB ± 0%  -10.60%  (p=0.000 n=10+10)

name                                                                                                                   old allocs/op  new allocs/op  delta
RenderProjectResultsWithEnableDiffMarkdownFormat/single_successful_plan_with_diff_markdown_formatted/verbose_true-10        364 ± 0%       214 ± 0%  -41.29%  (p=0.000 n=10+9)
RenderProjectResultsWithEnableDiffMarkdownFormat/single_successful_plan_with_diff_markdown_formatted/verbose_false-10       358 ± 0%       207 ± 0%  -42.05%  (p=0.000 n=10+10)

@jamengual
Copy link
Contributor

Amazing, and so fast to reply, thanks so much @inkel

@jamengual jamengual merged commit 04f1d06 into runatlantis:main Jan 4, 2023
@inkel inkel deleted the inkel/plan_success_summary_improvement branch January 5, 2023 17:14
@nitrocode nitrocode added this to the 0.22.2 milestone Jan 8, 2023
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
quick-win Is obviously something Atlantis should do and will take <4 hrs work
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants