Incremental java compile in Gradle 2.1 is very slow

Hi,

I just tried out the incremental compile feature from Gradle 2.1. I understand that the initial build is slower, due to the analysis that is necessary for the feature but in our build the compile time increased by a factor > 10 (e.g compile time for first module increased from <10sec to >4min). Is this expected behaviour? Also I noticed that CPU utilization was quite low. What is the expected performance hit you would expect for the initial build?

Regards, Mario

Hi Mario,

There’s a lot of room for optimisation in our current implementation. We’ve approached this with the idea of making it work reliably in the wild, and then to continuously optimise as we get more data from the field.

Can you provide some stats about your codebase that you are trying this on?

Hi Luke,

the codebase is an JBoss based .ear projekt with about 500000 Lines of Java code in 50 modules. There are quite some third party dependencies, so the classpath for the compile might be relativly large. If you tell me what kind of stats would help you, I will try to gather them for you.

That tells me enough. Your dependency graph is going to be very large if it’s a typical JBoss based project.

Unfortunately there’s nothing you can do in the meantime to make this faster. Your only options are to trim the classpath, or to wait for us to optimize this to the point that it’s fast enough for you.

Hey,

Thanks a lot for reporting and trying out this new feature in the wild :slight_smile:

In order to correctly figure out what classes need to be recompiled, Gradle needs to perform bytecode analysis of all classes on the classpath. This includes looking into the jars, and some jars are quite big :slight_smile: Fortunately, we cache those results globally. So, if you have analyzed the groovy-all jar in one of the projects, it won’t be needed again. This cache is per Gradle version, so when you start using a new Gradle version (or different gradle user home), the initial build will be slower.

For sure we could improve the speed of the analysis or do more caching (for example, we should cache the hashes of classes like we do for incremental compilation - we don’t do it yet). If you can provide us any profiler data it would be awesome.

For the low CPU problem you could try running with --paralell. Depending on your multiproject topology, this could make run compilations of some projects in parallel, possibly speeding up the build (and using more cores).

This is great feedback and we will be improving the incremental compilation speed.

Hi,

I’m always happy to try out this features, as everything that speeds up our development cycle brings a real benefit to us!

I will not be able to share any code with you, but if you tell me what kind of profiling data would help you and how to gather it I will try my best to provide you with some data.

One other thing I noticed was that the initial incremental compilation was a lot faster on an SSD drive, so perhaps the HDD was the bottleneck in my initial test. Still, also on the SSD machine the non-incremental compile was faster than the incremental one, even when only changing a single file that get’s referenced by some other modules.

I’ll try to answer :slight_smile: BTW. Did you read the user guide section on the incremental compilation?

Do as little compilation as possible (i.e. don’t compile what is not affected by the code change). Is this a good explanation?

Yes. The high level goal is to make the dev experience better by offering faster and smarter builds. We want to compile as little classes as possible and ensure as little output class files are ‘changed’ after the compilation.

Does incremental compilation depend on the compiler or the mechanism (e.g. Gradle) calling the compiler and telling it what to do? So basically Gradle could do incremental compilation with every compiler, even javac? Or does Gradle need a special compiler and why?

Gradle uses standard java API for compilation (it’s what javac uses behind the hood). We perform bytecode analysis and select only specific classes for compilation.

Then there are build tools like ANT and Gradle. I always thought Gradle was doing incremental compilation even before 2.1? Is this incorrect?

Gradle’s compilation task was incremental ‘at the level of task’ since always. If you haven’t change any compilation inputs (source code, classpath, etc) and outputs are present/untampered, then the entire compilation task is considered UP-TO-DATE and compilation is skipped. The new 2.1 feature operates on the source class level. If only one class was changed, in theory it might be that only this single class needs to recompiled. This should save time and ensure that little outputs are changed (useful for use cases like jrebel).

Was Gradle always compiling the entire project even if just a single local variable in a private class was modified?

Yes :slight_smile:

Can you give a few major examples where a small change in a single class potentially affects the entire project and therefore demands compiling everything. I already mentioned global variables. What other examples are there?

For example, a class a non-private static constant. Due to compiler optimization we cannot detect all references to this constant from other classes via byte code analysis. So we recompile everything. Don’t do non-private constants :slight_smile:

Finally there is IDEA which seems to be married to Gradle eventually.

At some point yes but it’s not going to change soon (it needs a lot of effort). Currently compilation is separate in both tools and we strongly encourage that the IDE output dirs are kept separate from Gradle’s output dirs (that’s the default).

Hope that helps!

You can try running with -i and look for messages from the incremental compilation. We may find out why it’s longer when you change only single class.

Also, changing certain classes trigger full recompilation anyway. For example, changing a class with non-private constant trigger full recompilation because due to compiler optimization we cannot reliably infer references to this constant from the byte code.

Hope that helps!