Iñaki Villar
Posted on November 17, 2024
As modularization becomes increasingly common in Android projects, it increases the number of subprojects within a Gradle build. While modularization brings many benefits—such as improved software development practices and reduced build times by reusing tasks unaffected by code changes—it also has a side effect: the configuration time in Gradle projects increases as the project structure grows. For instance, the following graph represents a build executing the :help
task in a project containing between 100 and 1000 modules, incremented in steps of 100, during a fresh daemon build:
The Configuration Cache feature, introduced by the Gradle team, addresses this problem by caching the result of the configuration phase of the build, then reusing it in subsequent builds if no relevant changes have occurred. This feature made local development faster and easier to work with, enabling faster build cycles. However, as projects grow, new optimizations to the configuration cache are needed to continue providing the best possible developer experience.
Gradle 8.11 introduces new improvements to the configuration cache process, including per-project serialization and string deduplication. Additionally, it introduces a new incubating feature that enables storing and loading the configuration cache in parallel, resulting in improved performance.
To enable the feature, add the following flag in gradle.properties
:
org.gradle.configuration-cache.parallel=true
In this article, we will share the results of our experiments with the new Gradle 8.11 parallel configuration cache feature in the nowinandroid
project and explore how both local and CI builds can benefit from decreased configuration time.
Methodology
As always, before diving into the results, let's take a look at the experiment:
Project
The experiment uses a project forked from nowinandroid(latest commit).
The task used for this experiment is assembleDebug
.
Variants Experiment
- gradle_8_10, main branch using 8.10 and configuration cache.
- gradle_8_11, project using Gradle 8.11 and configuration cache.
- gradle_8_11_parallel, project using Gradle 8.11 and parallel configuration cache.
Scenarios
- Build with configuration cache miss:
- Dependencies prepopulated in the Gradle user home.
- Using clean agents.
- Build with configuration cache hit.
Environment
Github Action runner:
- Linux 6.5.0-1025-azure
- 4 cores
- JDK 17
- Xmx 4GB (Gradle-Kotlin)
Scenario Configuration Cache Miss
100 iterations for each variant using Telltale to orchestrate the execution.
Scenario Configuration Cache Hit
20 iterations for each variant using Gradle Profiler and Telltale.
Metrics
- The build metrics data is published to Develocity using build scans.
- With the Develocity API, experiment configuration cache metrics are now accessible via the new endpoint:
/api/builds/{id}/gradle-configuration-cache
, introduced in Develocity 2024.2. Example output:
{
"result": {
"outcome": "HIT",
"entrySize": 1254385,
"load": {
"duration": 500,
"hasFailed": false
}
}
}
Results
Configuration cache entry size
Before diving into the results related to durations, we first analyze the impact of these optimizations on reducing the size of the cache artifact:
- Gradle 8.11 has reduced the size of the cache entry for the assembleDebug task by 14.67%.
- Enabling parallel configuration results in the same cache artifact size as Gradle 8.11.
Configuration time with cache miss (dependencies already downloaded)
This scenario simulates a configuration cache miss, ensuring that all dependencies are pre-downloaded to eliminate the impact of network latency during dependency resolution. The median result for each variant is as follows:
- Gradle 8.11 reduced the configuration time by 4.26%
- Gradle 8.11 with parallel configuration cache reduced the configuration time by 8.16%
Configuration Cache Miss (using clean agents)
In this scenario, we are working with clean agents that request dependencies. The median result for each variant is as follows:
- Gradle 8.11 reduced the configuration time by 14.5%.
- Gradle 8.11 with parallel configuration further reduced configuration time by 31.72%.
Here, we have some very interesting results showing that using the parallel configuration cache reduces configuration time by 85 seconds. This highlights the significant benefits of enabling parallel configuration cache.
Configuration cache operations (dependencies already downloaded)
Storing cache entry
Using the Develocity API, we extracted the store operation duration for each variant in the scenario where the dependencies are already provided:
- Comparing 8.10 to 8.11 with parallel configuration cache shows a significant improvement of 29.58% on the median for the operation of saving the configuration cache entry.
Load cache entry
Using the Develocity API, this time we extracted the load operation duration for each variant in the scenario where the dependencies are already provided:
- Gradle 8.11: Offers a significant improvement over 8.10 for this metric, reducing the value by over 20%.
- Parallel Configuration in 8.11 increases the value slightly compared to default 8.11, it still performs better than 8.10 overall.
Configuration cache operations (using clean agents)
In the scenario with clean agents, we noticed high variability due to non-deterministic connectivity operations. For instance, in the case of the load operation:
and for the store operation:
We observed a slight improvement when using 8.11 parallel, but the visualization is noisy. For this reason, we chose to present the percentiles instead:
Storing cache entry
Gradle 8.11 offers modest improvements over 8.10 across all percentiles, particularly at the median and upper quartile levels.
Gradle 8.11 with Parallel Configuration Cache dramatically reduces metrics across all percentiles, like the median with an improvement of 65%.
Loading cache entry
- Gradle 8.11 median value improved (decreased) by approximately 11.6% when moving from Gradle 8.10.
- Gradle 8.11 parallel configuration median value improved (decreased) by approximately 14.6% when moving from Gradle 8.10.
Configuration Cache Hit
For the second scenario, using Gradle Profiler, we iterated over the same runner executing the same build. In this case, the configuration cache was hit, and we measured the median configuration time for those builds. The results are as follows:
We noticed a slight improvement in the median configuration time when hitting the cache. However, given the size of the project, the reduction in duration is not significant.
Configuration Cache Hit - load time
Finally, for the same scenario—hitting the configuration cache—we analyzed the output of the Develocity endpoint for the load operation:
- Gradle 8.11 and Gradle 8.11 parallel offer significant improvements over 8.10, with reductions of 30.6% and 26%, respectively. However, in the context of this project, the value of the load operation in the experiment is not significant.
References Experiment
Build Scans
Configuration cache miss scenarios
Dependencies Cache | Clean Agents | |
---|---|---|
Gradle 8.10 | Build Scans | Build Scans |
Gradle 8.11 | Build Scans | Build Scans |
Gradle 8.11 Parallel | Build Scans | Build Scans |
Configuration cache hit scenarios
Gradle Profiler Builds | |
---|---|
Gradle 8.10 | Build Scans |
Gradle 8.11 | Build Scans |
Gradle 8.11 Parallel | Build Scans |
Experiments
Experiment | Scenario | Results |
---|---|---|
Gradle 8.10 vs Gradle 8.11 | Dependencies Cache | Experiment |
Gradle 8.11 vs Gradle 8.11 Parallel | Dependencies Cache | Experiment |
Gradle 8.10 vs Gradle 8.11 | Clean | Experiment |
Gradle 8.11 vs Gradle 8.11 Parallel | Clean | Experiment |
Gradle 8.10 vs Gradle 8.11 | Gradle Profiler | Experiment |
Gradle 8.11 vs Gradle 8.11 Parallel | Gradle Profiler | Experiment |
Final notes
Analyzing the results, we've observed a significant improvement in configuration time when enabling org.gradle.configuration-cache.parallel
. This feature not only reduces the configuration time but also reduces the configuration cache entry size. This means the Gradle model saved in the cache uses less space on disk. As a result, the cache is stored and loaded faster, which is especially helpful for big and complex projects.
Of course, the results are based on the project under experiment and may vary depending on your project, but we strongly recommend enabling org.gradle.configuration-cache.parallel
to take advantage of these improvements.
Happy Building!
Posted on November 17, 2024
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.