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: generate crds in parallel and optimize code #4644

Merged
merged 1 commit into from
Jan 28, 2023

Conversation

nicoloboschi
Copy link
Contributor

@nicoloboschi nicoloboschi commented Dec 5, 2022

Description

In my project I have 10 CRDs classes.
I've taken a CPU flamegraph and I figured out the usage of java streams api adds a relevant overhead while visiting specs/status classes via reflection.

Also the visitors can access the crd in parallel since the base typeref is stateless.

Changes included:

  • Enable concurrent crd visitors using nCPU threads.
  • Moved some hot path code from using streams to old plain java for-loop

FYI I did a similar fix in sundrio: sundrio/sundrio#352

The net result of this patch is that generation time moved from ~30 seconds to to ~11 seconds.

Type of change

  • Bug fix (non-breaking change which fixes an issue)
  • Feature (non-breaking change which adds functionality)
  • Breaking change (fix or feature that would cause existing functionality to change
  • Chore (non-breaking change which doesn't affect codebase;
    test, version modification, documentation, etc.)

Checklist

  • Code contributed by me aligns with current project license: Apache 2.0
  • I Added CHANGELOG entry regarding this change
  • I have implemented unit tests to cover my changes
  • I have added/updated the javadocs and other documentation accordingly
  • No new bugs, code smells, etc. in SonarCloud report
  • I tested my code in Kubernetes
  • I tested my code in OpenShift

@nicoloboschi nicoloboschi marked this pull request as ready for review December 5, 2022 17:33
@andreaTP
Copy link
Member

andreaTP commented Dec 5, 2022

Hi @nicoloboschi , thanks for this PR! This sounds super interesting indeed!
But, since I have already experienced issues with concurrency + sundrio + crd-gen, I need to summon @metacosm and @iocanel to review this change 🙏

@andreaTP
Copy link
Member

andreaTP commented Dec 6, 2022

@nicoloboschi are you still actively working on this? if that's the case, would you mind converting to Draft?

@nicoloboschi
Copy link
Contributor Author

@nicoloboschi are you still actively working on this? if that's the case, would you mind converting to Draft?

it's ready for review

List<CompletableFuture<Void>> futures = new ArrayList<>();
if (config.specClassName().isPresent()) {
futures.add(CompletableFuture.runAsync(() -> {
TypeDefBuilder builder = new TypeDefBuilder(def);
Copy link
Member

Choose a reason for hiding this comment

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

@andreaTP mentioned a change in semantics, which is true.
However, it shouldn't make a difference as in this particular case we are not interested in building something (we completely ignore that), but instead we just need to traverse the object graph.

So, in my book it's acceptable but I would add a comment so that it's clear to people less familiar with the code.

Since, we are interested in optimizing things, I would also try to pass both visitors in a single accept invocation (it does accept var-arg). Since, that would reduce the graph traversals to half it might make a difference for large objects.

Copy link
Contributor Author

@nicoloboschi nicoloboschi Dec 13, 2022

Choose a reason for hiding this comment

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

However, it shouldn't make a difference as in this particular case we are not interested in building something (we completely ignore that), but instead we just need to traverse the object graph.

yes, exactly, we just do a LOT of recursive calls and the only output is stored in the visitor object itself (getPath())

@iocanel I've already tried with the var-args solution but it doesn't help.

Sharing my dummy benchmark numbers

  • using quarkus-operator-sdk mojo to generate crd
  • jdk 17
  • Intel mac 6 core
  • 8 CRD in the project with ~200 props in total, spec and status classes in each one
  • 3 rounds per solution

Results:

  • current version: ~40s
  • concurrency of 5: ~18s
  • var args: ~45s

Copy link
Member

@iocanel iocanel left a comment

Choose a reason for hiding this comment

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

Overall, looks good.
Added a comment with two suggestion, which I consider nice ot have.

@sonarcloud
Copy link

sonarcloud bot commented Dec 14, 2022

Kudos, SonarCloud Quality Gate passed!    Quality Gate passed

Bug A 0 Bugs
Vulnerability A 0 Vulnerabilities
Security Hotspot A 0 Security Hotspots
Code Smell A 5 Code Smells

83.6% 83.6% Coverage
0.0% 0.0% Duplication

@nicoloboschi
Copy link
Contributor Author

@iocanel any chance we can merge this?

this is my latest comment about performances comparison

  • using quarkus-operator-sdk mojo to generate crd
  • jdk 17
  • Intel mac 6 core
  • 8 CRD in the project with ~200 props in total, spec and status classes in each one
  • 3 rounds per solution

Results:

  • current version: ~40s
  • concurrency of 5: ~18s
  • var args: ~45s

@nicoloboschi
Copy link
Contributor Author

@iocanel @andreaTP any chance to see this pull moving forward? 🤞

@andreaTP
Copy link
Member

Hi @nicoloboschi and thanks again for your effort!
I'm leaving space to @metacosm and @iocanel here, maybe also @manusa wants to chip into this.

@manusa
Copy link
Member

manusa commented Jan 17, 2023

Hi @nicoloboschi
I still need to review this and analyze if it might cause trouble in certain environments. AFAIK running the generation in several parallel processes might lead to inconsistent results.
One of the options is to make this a configuration option.

@nicoloboschi
Copy link
Contributor Author

Hi @nicoloboschi I still need to review this and analyze if it might cause trouble in certain environments. AFAIK running the generation in several parallel processes might lead to inconsistent results. One of the options is to make this a configuration option.

@manusa I can totally add a flag. I haven't find any configuration present at the moment. Do you have any suggestion on how to make it configurable?

For example, I could add a system property but I'm not sure how it is supposed to be set if someone is using the crd-generator-apt module

@andreaTP
Copy link
Member

Do you have any suggestion on how to make it configurable?

Currently, the crd generator is purely using annotations and has no additional configuration:
https://github.com/fabric8io/kubernetes-client/tree/009974fe5fe634cbd6d1210780b99ff82e251f2d/crd-generator/api/src/main/java/io/fabric8/crd/generator/annotation

@nicoloboschi
Copy link
Contributor Author

  • I've made it configurable and disabled by default.
  • I added an option in CRDGenerator for enabling the parallel execution.
  • For the apt processor I've added an option io.fabric8.crd.generator.parallel.
  • I added the apt processor option example in the test pom.xml
<compilerArgs>
            <arg>-Aio.fabric8.crd.generator.parallel=false</arg>
</compilerArgs>
  • I've added CRDGenerator tests with parallel enabled
  • I tested locally the crd-generator/tests suite with parallel enabled but I've left it disabled since it's better to test the default configuration in this case

@andreaTP let me know if I missed something

@andreaTP
Copy link
Member

I just skimmed through on the phone, and, making parallel execution optional makes a lot of sense.
I think that the Maven setting is a bit "hidden" and you probably want to mention it in the docs.

I might be wrong, but it looks like there are a few tiny bits that are not source compatible (which is appropriate to merge before 6.4) and @metacosm would need to have a look and follow up in the Quarkus Operator SDK.

@nicoloboschi
Copy link
Contributor Author

I created the related pr in for the quarkus sdk: quarkiverse/quarkus-operator-sdk#488 (it's only a draft since we need this to get released)

@andreaTP
Copy link
Member

Thanks for the follow-ups @nicoloboschi , LGTM.

Copy link
Collaborator

@metacosm metacosm left a comment

Choose a reason for hiding this comment

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

LGTM but I haven't checked downstream projects yet.

@nicoloboschi
Copy link
Contributor Author

@gastaldi I've addressed your comments, please review it again

Copy link
Contributor

@gastaldi gastaldi left a comment

Choose a reason for hiding this comment

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

LGTM. I'd appreciate if you could please squash the commits. Good job!

## Experimental

### Generate CRDs in parallel
Starting from 6.4, it's possible to speed up the CRDs generation by using parallel computation.
Copy link
Contributor

Choose a reason for hiding this comment

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

6.4.0 is already released, since this change doesn't break the API it could be introduced in a patch 6.4.x release

@@ -68,6 +69,11 @@ public CRDGenerator withOutput(CRDOutput<? extends OutputStream> output) {
return this;
}

public CRDGenerator withParallelGenerationEnabled(boolean parallel) {
Copy link
Contributor

Choose a reason for hiding this comment

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

Just an idea: what if the ExecutorService is passed instead? That would give a better control over the execution.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

In general I think this makes sense.
In this specific case, given the crd generation is a short-running task, I don't believe it's worth to manage the thread pool at higher level

@nicoloboschi
Copy link
Contributor Author

Fixed the doc, rebased to the current master and squashed @gastaldi

Copy link
Member

@manusa manusa left a comment

Choose a reason for hiding this comment

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

Awesome work, thx!

@manusa manusa added this to the 6.5.0 milestone Jan 27, 2023
@sonarcloud
Copy link

sonarcloud bot commented Jan 27, 2023

SonarCloud Quality Gate failed.    Quality Gate failed

Bug A 0 Bugs
Vulnerability A 0 Vulnerabilities
Security Hotspot A 0 Security Hotspots
Code Smell A 1 Code Smell

64.5% 64.5% Coverage
2.4% 2.4% Duplication

@manusa manusa merged commit a1f6505 into fabric8io:master Jan 28, 2023
@nicoloboschi nicoloboschi deleted the crd-parallel branch January 31, 2023 21:37
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.

None yet

6 participants