An open API service indexing awesome lists of open source software.

https://github.com/egorand/performance

Optimizing Gradle Build Performance
https://github.com/egorand/performance

Last synced: 2 months ago
JSON representation

Optimizing Gradle Build Performance

Awesome Lists containing this project

README

        

= Improving the performance of Gradle builds
:toclevels: 2
:numbered:
:source-language: groovy

Waiting is a part of life. Whether you’re waiting in line at a cinema or for the release of the latest must-have gadget, it’s something you learn to deal with. The same is often the case for builds and the waiting usually blocks you from doing something more productive. And the longer the build takes to complete, the more chance you'll be distracted by something else in the meantime. On top of that, since you may be repeatedly running the build many times a day, even small periods of waiting can add up to significant disruption.

All this means that it’s worth investing some time and effort into ensuring that your build runs as fast as possible. This guide offers several avenues you can explore to make a build run faster, along with plenty of detail on what sorts of things can adversely affect the performance of a build and why. Let’s start with some quick wins.

## Easy improvements

A guide on performance tuning would normally start with profiling and something about premature optimisation being the root of all evil, etc., etc. Profiling is definitely important and we discuss it later in the guide, but there are some things you can do that will impact all your builds for the better at the flick of a switch - metaphorically speaking.

### Enable the daemon

The Gradle daemon is a mechanism for improving the performance of Gradle. As of Gradle 3.0, the daemon is enabled by default but if you are using an older version, you should definitely enable it. You will see big improvements in build speed by doing so. You can learn how to do that https://docs.gradle.org/2.14/userguide/gradle_daemon.html[in the 2.14 user guide].

[CAUTION]
====
You should take particular care when using the daemon with a build that calls into third-party Java `main()` methods, such as those provided with JVM-based compilers. It's tempting to run Java code from within the Gradle VM in order to avoid the cost of spawning a new process, but if that code holds onto memory after it finishes, then it causes problems for the daemon.

For the sake of daemon stability, you should use the `JavaExec` task to execute the code in a separate process.
====

The recommendation for pre-3.0 versions of Gradle is to enable the daemon for developer machines and disable it for continuous integration servers. This is because the daemon is a background process that may require manual intervention from time to time and that’s not always easy when the process is on a separate machine from where you’re sitting. You also need to factor in whether you run many builds with different VM configurations, which will lead to each build having its own daemon and a potential sea of daemon processes at any one time.

Nonetheless, if build times on your CI server hurt developer productivity, you should definitely consider enabling the daemon there. Alternatively, upgrade to the latest version of Gradle to benefit from improvements in the robustness of the daemon.

### Use latest Gradle and JVM versions

The Gradle team works regularly on improving the performance of different aspects of Gradle builds. If you’re using an old version of Gradle, you’re missing out on the benefits of that work. Try upgrading to the latest version of Gradle to see what kind of impact it has. Doing so is low risk because very few things break between minor versions of Gradle.

Going up a major version is often just as easy and definitely recommended when available. Just be aware that this is when deprecations become failures. So be sure to fix those deprecation warnings!

As Gradle runs on the JVM, improvements in the performance of the latter will often benefit Gradle. Hence, you should consider running Gradle with the latest major version of the JVM.

### Configure on demand

Imagine you have 300 subprojects all managed by a single Gradle build. Even if the configuration time for each project is just half a second, that means every build will take at last 300 x 0.5s = 2.5 minutes! That’s why it’s particularly crucial to keep a close eye on configuration times for projects that have a lot of modules.

With that in mind, wouldn’t it be better if Gradle only configured the projects that were actually required to execute the requested task(s)? Of course, and that’s where the `--configure-on-demand` command line option comes in. It avoids a lot of unnecessary configuration at the cost of working out which projects are required. We wouldn’t recommend using it if you only have a small number of quick-to-configure subprojects, but otherwise you should try it.

NOTE: Configure on demand will only work reliably when you have decoupled projects or explicit (rather than implicit) inter-project dependencies. You can learn more about https://docs.gradle.org/current/userguide/multi_project_builds.html#sec:decoupled_projects[decoupled projects] in the User Manual.

You can make configure on demand the default for a project by adding the following setting to its _gradle.properties_ file:

[source,java]
org.gradle.configureondemand=true

### Parallel builds

Few non-trivial builds consist of just one project and when you have a mult-project build, some of those projects are usually independent of one another. Yet Gradle will only ever run one task at a time by default, regardless of the project structure. By using the `--parallel` switch, you can force Gradle to execute independent subprojects - those that have no implicit or explicit project dependencies between one another - in parallel, allowing it to run multiple tasks at the same time as long as those tasks are in different projects.

You could see big improvements in build times as soon as you enable parallel builds. The extent of those improvements depends on your project structure and how many explicit dependencies you have between them. A build whose execution time is dominated by a single project won't benefit much at all, for example. Or one that has lots of inter-project dependencies resulting in few projects that can be executed in parallel. But most multi-project builds should see a worthwhile boost to build times.

NOTE: Parallel builds have similar requirements to configure on demand with regard to decoupled projects. In addition, you should read more about them in https://docs.gradle.org/current/userguide/multi_project_builds.html#sec:parallel_execution[the User Manual] before using them extensively. You need to be more careful in your build configuration and execution than normal to use parallel builds reliably. For example, you should currently avoid running the `clean` task in combination with other tasks in a parallel build - the User Manual explains why in a warning.

You can also make building in parallel the default for a project by adding the following setting to the project's _gradle.properties_ file:

[source]
org.gradle.parallel=true

That’s the end of our quick wins. From here on out, improving your build performance will require some elbow grease. We start with perhaps the most important step: finding out which bits of your build are slow and why.

## Profile, profile, profile

As with any attempt to optimize, your first instinct should be to profile the system you’re interested in. Gradle provides two mechanisms for doing this and both of them give you reports that you can view in a browser. Let's take a look at these two mechanisms - build scans and profile reports - before then discussing how to interpret the information provided in the reports.

### Using Gradle build scans

Build scans are a feature provided by Gradle Inc. that aggregate information across multiple build runs and make those diagnostics available to you online as a report. You can

* Easily share scans between developers and build masters
* Track build performance over time
* More easily identify the source of a problem
* Identify issues specific to a single developer site

Gradle won't generate build scans automatically, but you can easily enable them by following these steps:

1. Apply the _build-scan_ plugin https://scans.gradle.com/setup/step-1[as described on Gradle.com]
2. Run your builds with the `-Dscan` option, e.g. `gradle build -Dscan`

At the end of the build, Gradle displays the URL where your build scan awaits your attention.

The build scans themselves provide a lot of information, but the main area of interest in the early stages of diagnosis is the performance page. To get there, follow the link highlighted in the following screenshot of the build scan home page:

image::build-scan-home.png[title="Performance page link on build scan home page"]

The performance page gives you a breakdown of how long different stages of your build took to complete. As you can see from the following screenshot, you get to see how long Gradle took to start up, configure the build's projects, resolve dependencies, and execute the tasks. You also get details about environmental properties, such as whether a daemon was used or not.

image::build-scan-performance-page.png[title="Build scan performance page"]

We will look into the different categories presented in the report shortly. You can also learn more about build scans https://gradle.com[at Gradle.com].

### Profile report

If you don't have internet access or have some other reason not to use build scans, it’s still trivially easy to profile a Gradle build. Simply add the `--profile` option to the command line args:

gradle --profile

This will result in the generation of an HTML report that you can find in the _build/reports/profile_ directory of the _root_ project. Each profile report has a timestamp in its name to avoid overwriting existing ones.

Similar to build scans, the report itself displays a breakdown of the time taken to run the build for a given set of task arguments. Here’s a screenshot of a real profile report showing the different categories that Gradle uses:

image::gradle-profile-report.png[title="An example profile report", alt="Sample Gradle profile report"]

Each of the main categories - _Configuration_, _Dependency Resolution_, and _Task Execution_ - may reveal different time sinks that you may want to tackle. We’ll go through those categories in later sections, detailing the types of issue you may encounter for each one. Before then, let’s take a look at some of the items in the summary.

### Understanding the profile report categories

Both build scans and the local profile reports break build execution down into the same categories. We'll now look at those categories, what they mean, and what sorts of problems you can identify with them.

#### Startup

This reflects Gradle’s initialization time, which consists mostly of

* JVM initialization and class loading
* Downloading the Gradle distribution if you’re using the wrapper
* Starting the daemon if a suitable one isn’t already running
* Time spent executing any Gradle initialization scripts

Even if a build execution has a long startup time, a subsequent run will usually see a dramatic drop off in the startup time. The main reason for a build's startup time to be persistently slow is a problem in your init scripts. Double check that the work you’re doing there is necessary and as performant as possible.

#### Settings and _buildSrc_

Soon after Gradle has got itself up and running, it initializes your project. This commonly just means processing your _settings.gradle_ file, but if you have custom build logic in a _buildSrc_ directory, that gets built as well.

The sample profile report shows a time of just over 8 seconds for this category, the vast majority of which was spent building the _buildSrc_ project. This part fortunately won’t take so long once _buildSrc_ is built once as Gradle will consider it up to date. The up-to-date checks still take a little time, but nowhere near as much. If you do have problems with a persistently time consuming _buildSrc_ phase, you should consider breaking it out into a separate project whose JAR artifact is added to the build's classpath.

The _settings.gradle_ file rarely has computationally or IO expensive code in it. If you find that Gradle is taking a significant amount of time to process it, you should use more traditional profiling methods, such as timing statements in _settings.gradle_ or a profiler, to determine why.

#### Loading projects

It normally doesn’t take a significant amount of time to load projects, nor do you have any control over it. The time spent here is basically a function of the number of projects you have in your build.

The rest of the summary relates to the main categories, which we cover in detail in the next sections. Before we do that, there's one more tool available to you for diagnosing performance problems: Gradle build scans.

## Configuration

As the user guide describes in https://docs.gradle.org/current/userguide/build_lifecycle.html[the build lifecycle chapter], a Gradle build goes through three phases: initialization, configuration, and execution. The important thing to understand here is that in non-native Gradle builds, configuration code always executes regardless of which tasks will run. That means any expensive work performed during configuration will permanently cripple the build, even for such things as `gradle help` and `gradle tasks`.

The profile report will help you identify which projects take the most time to configure, but that’s all. The next few subsections introduce techniques that can help improve the configuration time and explain why they work.

### Apply plugins judiciously

Every plugin that you apply to a project adds to the overall configuration time. Some plugins have a greater impact than others. That doesn’t mean you should avoid using plugins, but you should take care to only apply them where they’re needed. For example, it’s easy to apply plugins to all projects via `allprojects {}` or `subprojects {}` even if not every project needs them.

Ideally, plugins should not incur a significant configuration-time cost. If they do, the focus should be on improving the plugin. Nonetheless, in projects with many modules and a significant configuration time, you should spend a little time identifying any plugins that have a notable impact. The only reliable way to do this is by running a build twice: once with the plugin applied and once without.

### Avoid expensive or blocking work

This is fairly obvious based on what we’ve already said about the configuration phase, but it’s not hard to accidentally break this rule. It’s usually clear when you’re encrypting stuff or calling remote services during configuration if that code is in a build file. But logic like this is more often found in plugins and occasionally custom task classes, where it’s easy to forget which phase your code is running in. Things only get harder to track when they're factored into short methods and multiple classes.

This is not an argument for putting all your build logic into build scripts. That’s generally a recipe for unmaintainable builds. However, if your profile report is indicating that the build is spending more time than expected in the configuration phase, you should start looking at your plugins and custom task classes. A task should generally rely on the build script or plugin that instantiates it to set its properties, rather than performing the configuration itself. If you're familiar with the principle of Inversion of Control (IoC), that's what you should be aiming for.

### Statically compile tasks and plugins

Plugins and occasionally tasks perform work during the configuration phase. These are often written in Groovy for its concise syntax, API extensions to the JDK, and functional methods using closures. However, it’s important to bear in mind that there is a small cost associated with method calls in dynamic Groovy. When you have lots of method calls repeated across lots of projects, the cost can add up.

In general, we recommend that you use either `@CompileStatic` on your Groovy classes (where possible) or write those classes in a statically compiled language, such as Java. This only really applies to large projects or plugins that you publish publicly (because they may be applied to large projects by other users). If you do need dynamic Groovy at any point, simply use `@CompileDynamic` for the relevant methods.

*Note* The DSL you’re used to in the build script relies heavily on Groovy’s dynamic features, so if you want to use static compilation in your plugins, you will have to switch to using Gradle’s core API. For example, to create a new copy task, you would use code like this:

[source]
----
project.tasks.create("copyFiles", Copy) { Task t ->
t.into "${project.buildDir}/output"
t.from project.configurations.getByName("compile")
}
----

You can see how this example uses the `create()` and `getByName()` methods, which are available on all Gradle “domain object containers”, like tasks, configurations, dependencies, extensions, etc. Some collections have dedicated types, `TaskContainer` being one of them, that have useful extra methods like the `create()` method above that takes a task type.

If you do decide to use static compilation, we recommend using an IDE as it will quickly show errors due to unrecognised types, properties, and methods. You’ll also get auto-completion, which is always handy.

## Dependency resolution

Software projects rely on dependency resolution to simplify the integration of third-party libraries and other dependencies into the build. This does come at a cost as Gradle has to contact remote servers to find out about said dependencies and download them where necessary. Advanced caching helps speed things up tremendously, but you still need to watch out for a few pitfalls that we discuss next.

### Dynamic and snapshot versions

Dynamic versions, such as “2.+”, and snapshot (or changing) versions force Gradle to contact the remote repository to find out whether there’s a new version or snapshot available. By default, Gradle will only perform the check once every 24 hours, but this can be changed. Look out for `cacheDynamicVersionsFor` and `cacheChangingModulesFor` in your build files and initialization scripts in case they are set to very short periods or disabled completely. Otherwise you may be condemning your build users to frequent slower-than-normal builds rather than a single slower-than-normal build a day.

You may be able to use fixed versions - like 1.2 and 3.0.3.GA - in which case Gradle will always use the cached version. But if you want or need to use dynamic and snapshot versions, make sure you tune the cache settings according to your requirements.

### Favor dependency resolution during execution

Dependency resolution is an expensive process, both in terms of IO and computation. Gradle reduces - and eliminates in some cases - the required network traffic through judicious caching, but there is still work it needs to do. Why is this important? Because if you trigger dependency resolution during the configuration phase, you’re going to add a penalty to every build that runs.

The key question to answer is what triggers dependency resolution? The most common cause is the evaluation of the files that make up a configuration. This is normally a job for tasks, since you typically don’t need the files until you’re ready to do something with them in a task action. However, imagine you’re doing some debugging and want to display the files that make up a configuration through judicious caching. One way you can do this is by injecting a print statement:

[source]
task copyFiles(type: Copy) {
println ">> Compilation deps: ${configurations.compile.files}"
into "$buildDir/output"
from configurations.compile
}

The `files` property will force Gradle to resolve the dependencies, and in this example that’s happening during the configuration phase. Now every time you run the build, no matter what tasks you execute, you'll take a hit from the dependency resolution on that configuration. It would be better to add this in a `doFirst()` action.

Note that the `from()` declaration doesn’t resolve the dependencies because you’re using the configuration itself as an argument, not its files. The `Copy` task handles the resolution of the configuration itself during task execution, which is exactly what you want.

The performance page of build scans explicitly shows how dependency resolution time is split across project configuration and task execution, so it's easy to identify this particular issue. If you're using the older profile reports, a simple way to determine whether you’re resolving dependencies during configuration is to run

gradle --profile help

and look at the time spent on dependency resolution. This should be zero, so if it’s not, you’re resolving dependencies at configuration time. The report will also tell you which configurations are being resolved, which should help in diagnosing the source of the configuration-time resolution.

### Avoid unnecessary and unused dependencies

You will sometimes encounter situations in which you're only using one or two methods or classes from a third-party library. When that happens, you should seriously consider implementing the required code yourself in the project or copying it from an open source library if that's an option for you. Remember that managing third-party libraries and their transitive dependencies adds a not insignificant cost to project maintenance as well as build times.

Another thing to watch out for is the existence of _unused dependencies_. This can easily happen after code refactoring when a third-party library stops being used but isn't removed from the dependency list. You can use the https://github.com/nebula-plugins/gradle-lint-plugin[Gradle Lint plugin] to identify such dependencies.

### Minimize repository count

When Gradle attempts to resolve a dependency, it searches through each repository in the order that they are declared until it finds that dependency. This generally means that you want to declare the repository hosting the largest number of your dependencies first so that only that repository is searched in the majority of cases. You should also limit the number of declared repositories to the minimum viable number for your build to work.

One technique available if you're using a custom repository server is to create a virtual repository that aggregates several real repositories together. You can then add just that repository to your build file, further reducing the number of HTTP requests that Gradle sends during dependency resolution.

### Be careful with custom dependency resolution logic

Dependency resolution is a hard problem to solve and making it perform well simply adds to the challenge. And yet, Gradle still needs to allow users to model dependency resolution in the way that best suits them. That's why it has a powerful API for customizing how the dependency resolution works.

Simple customizations -- such as forcing specific versions of a dependency or substituting one dependency for another -- don't have a big impact on dependency resolution times. But if custom logic involves downloading and parsing extra POMs, for example, then the impact can be significant.

You should use build scans or profile reports to check that any custom dependency resolution logic you have in your build doesn't adversely affect dependency resolution times in a big way. And note that this could be custom logic you have written yourself or it could be part of a plugin that you're using.

## Task execution

The fastest task is one that doesn’t execute. If you can find ways to skip tasks you don’t need to run, you’ll end up with a faster build overall. In this section, we’ll discuss a few ways to achieve task avoidance in Gradle.

### Different people, different builds

It seems to be very common to treat a build as an all or nothing package. Every user has to learn the same set of tasks that have been defined by the build. In many cases this makes no sense. Imagine you have both front-end and back-end developers: do they want the same things from the build? Of course not, particularly if one side is HTML, CSS and Javascript, while the other is Java and servlets.

It’s important that a single task graph underpins the build to ensure consistency. But you don’t need to expose the entire task graph to everyone. Instead, think in terms of sets of tasks forming a restricted view upon the task graph, with each view designed for a specific group of users. Do front-end developers need to run the server side unit tests? No, so it would make no sense to force the cost of running the tests on those users.

With that in mind, consider the different workflows that each distinct group of users require and try to ensure that they have the appropriate “view” with no unnecessary tasks executed. Gradle has several ways to aid you in such an endeavour:

* Assign tasks to appropriate groups
* Create useful aggregate tasks (ones that have no action and simply depend on a set of other tasks, like `assemble`)
* Defer configuration via `gradle.taskGraph.whenReady()` and others, so you can perform verification only when it's necessary

It definitely requires some effort and an investment in time to craft suitable build views, but think about how often users run the build. Surely that investment is worth it if it saves users time on a daily basis?

### Incremental build

You can can avoid executing tasks, even if they’re required by a user. If neither a task’s inputs nor its output have changed since the last time it was run, why would it need to run again? It’s up to date, which is why you often see the text `UP-TO-DATE` next to task names when running a build.

Incremental build is the name Gradle gives to this feature of checking inputs and outputs to determine whether a task needs to run again or not. Most tasks provided by Gradle take part in incremental build because they have been defined that way. You can also make your own tasks integrate with incremental build, as described in the user guide. The basic idea is to mark the task’s properties that have an impact on whether a task needs to run. You can learn more https://docs.gradle.org/current/userguide/more_about_tasks.html#sec:up_to_date_checks[in the user guide].

Incremental build is definitely a big boon on the whole, as it helps bring build times down significantly. You do need to be aware, though, that it incurs a cost as well, even for a clean build. This is because it needs to generate and verify checksums among other things. This cost is normally insignificant compared to the execution time of a task, but if all your tasks complete in less than a tenth of a second, incremental build may be slower.

You can easily identify good candidates for incremental build or tasks that aren’t up to date when they should be by looking at the Task Execution tab of the build profile report. The tasks are sorted by longest duration first, making it easy to pick out the slowest tasks. If they’re taking longer than half a second, you should probably consider enabling incremental build on them. You can also take the safe approach of making all tasks incremental.

### Partial builds

Incremental build definitely improves build times, but you need to remember that the up-to-date checks still take time. This has important implications for multi-project builds that have a large number of subprojects. If the task you want to execute ultimately depends on the execution of twenty other subprojects, you have to wait until the build has finished checking those before it gets round to your task. Some of them may even have non-incremental tasks that end up running, even if nothing has changed.

Gradle offers a nice shortcut if you know that a task's project dependencies haven't changed: use the `-a` command line option. This forces Gradle to effectively ignore all the dependent projects and only execute the required tasks that are defined in the target project. Project dependencies will still be included on the appropriate classpaths, so the project will build as before. Just be sure there haven't been any changes to the projects the target depends on!

Gradle also supports other forms of partial build via the _base_ plugin, which adds the following tasks:

* `buildNeeded` - will execute the `build` task in the target project and all those projects it depends on. This verifies that the projects you depend on are working correctly. If that's not the case, they may break the target project's tests or some other part of the build.
* `buildDependents` - will execute the `build` task in the target project and all projects that depend on it. This checks that you haven't broken those projects after making some changes.

These tasks are slower than just running `build` in the target project as they do more work, but they are an effective alternative to running `gradle build`, which runs `build` in _all_ the projects of a multi-project build.

## Other performance tweaks

You will sometimes come across tasks that need to run, but simply take a while. In such cases, you need to look at the task implementation. Or, in the case of third party tasks, such as those provided with Gradle, investigate the task configuration to see whether there are options that will improve the task execution time.

As the final stage of this guide, we’ll look at useful configuration for some of the core Gradle tasks.

### Boost daemon's heap size

Running builds in memory-constrained environments will have a significant and detrimental impact on the performance of those builds as the garbage collector has to do a lot more work. Attach JConsole or VisualVM to a Gradle daemon process to see whether it's using close to the maximum heap size. If it is, increase the max heap size through this property in _gradle.properties_:

org.gradle.jvmargs=-Xmx2048M

You should immediately see an improvement in build times once you've done this.

### Running tests (JVM)

A significant proportion of the build time for many projects consists of the test tasks that run. These could be a mixture of unit and integration tests, with the latter often being significantly slower. Gradle has a few ways to help your tests complete faster:

* Parallel test execution
* Process forking options
* Disable report generation

Let’s look at each of these in turn.

#### Parallel test execution

Gradle will happily run multiple test cases in parallel, which is useful when you have several CPU cores and don’t want to waste most of them. To enable this feature, just use the following configuration setting on the relevant `Test` task(s):

[source]
test.maxParallelForks = 4

The normal approach is to use some number less than or equal to the number of CPU cores you have. We recommend you use the following algorithm by default:

[source]
test.maxParallelForks = Runtime.runtime.availableProcessors().intdiv(2) ?: 1

Note that if you do run the tests in parallel, you will have to ensure that they are independent, i.e. don’t share resources, be that files, databases or something else. Otherwise there is a chance that the tests will interfere with each other in random and unpredictable ways.

#### Forking options

Gradle will run all tests in a single forked VM by default. This can be problematic if there are a lot of tests or some very memory-hungry ones. One option is to run the tests with a big heap, but you will still be limited by system memory and might encounter heavy garbage collection that slows the tests down.

Another option is to fork a new test VM after a certain number of tests have run. You can do this with the `forkEvery` setting:

[source]
test.forkEvery = 100

Just be aware that forking a VM is a relatively expensive operation, so a small value here will severely handicap the performance of your tests.

#### Report generation

Gradle will automatically create test reports by default regardless of whether you want to look at them. That report generation takes time, slowing down the overall build. Reports are definitely useful, but do you need them every time you run the build? Perhaps you only care if the tests succeed or not.

To disable the test reports, simply add this configuration:

[source]
test {
reports.html.enabled = false
reports.junitXml.enabled = false
}

This example applies to the default `Test` task added by the Java plugin, but you can also apply the configuration to any other `Test` tasks you have.

One thing to bear in mind is that you will probably want to conditionally disable or enable the reports, otherwise you will have to edit the build file just to see them. For example, you could enable the reports based on a project property:

[source]
test {
if (!project.hasProperty("createReports")) {
reports...
}
}

### Compiling Java

The Java compiler is quite fast, especially compared to other languages on the JVM. And yet, if you’re compiling hundreds of non-trivial Java classes, even a short compilation time adds up to something significant. You can of course upgrade your hardware to make compilation go faster, but that can be an expensive solution. Gradle offers a couple of software-based solutions that might be more to your liking:

* Compiler daemon
* Incremental compilation

Both of these are incubating at the time of writing, but they are worth experimenting with if you’re desperate to eke out better build performance.

### Compiler daemon

The Gradle Java plugin allows you to run the compiler as a separate process by using the following configuration for any `JavaCompile` task:

.options.fork = true

or, more commonly, to apply the configuration to _all_ Java compilation tasks:

tasks.withType(JavaCompile) {
options.fork = true
}

This has two benefits:

1. Gradle can handle compilation of very large numbers of source files concurrently without forcing you to increase the heap size of the main Gradle process or daemon.
2. The compiler process is reused between builds, significantly reducing the overall compilation times.

It's unlikely to be useful for small projects, but you should definitely consider it if a single task is compiling close to a thousand or more source files together.

### Incremental compilation

You may wonder why incremental compilation is an optional extra for Gradle rather than the default. Even IDEs have their own incremental compilers, right? The reason for this state of affairs is the standard Java compiler itself, the one that comes with the JDK.

The standard compiler does attempt to work out what files need recompiling based on a set of changes, but this is rather unreliable. The Java developers amongst you have probably encountered a few instances where you had to run a clean first to fix some compilation issues. So Gradle avoids the potential problems from this by not using the compiler to determine what needs to be recompiled.

Still, incremental compilation can provide real performance benefits, so version 2.1 of Gradle introduced it for Java for the first time. It’s still an incubating feature and may unfortunately have bugs, but it’s very easy to enable and disable. Simply use the following configuration:

[source]
compileJava.options.incremental = true

Just be aware that it is still an incubating feature. That said, you can expect continued improvements in future versions of Gradle.

## Suggestions for Android builds

Everything we have talked about so far applies to Android builds too, since they're based on Gradle. Yet Android also introduces its own performance factors, particularly around the CPU-intensive dexing process. Here we provide some additional ideas for improving Android builds specifically.

### Use the latest Android plugin

The Android plugin is similar to Gradle in that newer versions introduce improved build performance. For example, version 2.10 of the plugin enabled incremental compilation by default and added support for in-process dexing.

### Invest in fast CPUs

Since dexing is CPU-intensive, a significantly faster CPU will deliver a significantly faster build. Also note that dexing uses a significant amount of memory, so you should monitor the heap usage for your build when profiling it. You may find it pays to increase the maximum heap size for Gradle.

### Optimize multidex development builds

Creating multidex output for your project typically adds a significant amount of time to your build due to the merging process. You can mitigate this by setting a minimum SDK version of 21, which allows the Android plugin to perform more efficient dexing.

Of course, in most cases this isn't feasible in general because older versions of Android are still prevalent and many of you will want to support those devices. However, if you don't mind developing and testing against SDK 21+ only, then can set up a development flavor that targets a minimum SDK version of 21, while the production flavor targets an older version. This results in a faster build when working on the project code.

Here's an example partial configuration with the two flavors:

[source,groovy]
----
android {
productFlavors {
dev {
minSdkVersion 21
}
prod {
minSdkVersion 14
}
}
}
----

You can find out more about this feature in the https://developer.android.com/studio/build/multidex.html#dev-build[Android Studio user guide]. Whether you use this approach or not will depend on how you see the trade off between development build speed and consistency in versions between development and production testing.

### Use discretion when adding build types and flavors

The more build types and flavors you create, the longer it will take for Gradle to configure your project. So what you can do is wrap flavor and build type declarations in conditions - via `if` statements typically - such that whoever runs the build can switch flavors and build type on or off through project or system properties.

For example, if a developer only ever builds debug versions of a project, they could set a project property that disables the non-debug build types and the non-development flavors. They can still build whatever they need simply by passing different property values, but the default would result in a faster build.

This does make builds less maintainable, though, so you should only use this approach where you get a significant boost in build speed. Conventions in property names can help with the build maintenance too.

## Summary

Performance is a feature and the Gradle team are always attempting to make the Gradle defaults as fast as possible because they know that their users' time is valuable. Even so, Gradle supports a huge variety of builds, which means that the defaults won't always be ideal for _your_ project. That's why we introduced you to some settings and task options that allow you to tweak the behavior of the build in your favor. You should also familiarise yourself with any other available options on your long running tasks and with the generic Gradle build environment settings.

Beyond those settings, remember that the two big contributors to build times are configuration and task execution, although the base cost of the former drops with almost every major Gradle release. And as far as the configuration phase goes, you should now have a good idea of the pitfalls you need to avoid.

You have more control over task execution, since you can avoid running tasks or running them too often, and you can also code your own tasks to be as performant as possible. In the future, Gradle will offer more features to help with execution performance. Things like parallel task execution. You have plenty to look forward to!

In the meantime, we hope the ideas in this guide help you cut your build times and improve the overall user experience.