构建扫描与性能:Build Scan、Profiling、构建优化技巧
说实话,Gradle 构建慢起来是真要命。我见过不少团队,每天等构建的时间比写代码还多。你想想看,一次构建五分钟,一天十次就是五十分钟,一个月下来……嗯,这账不能细算。
这一章我们就来聊聊怎么给 Gradle 做「体检」和「瘦身」。核心就三件事:用 Build Scan 看清问题、用 Profiling 定位瓶颈、用优化技巧解决问题。
一、Build Scan:构建的「CT 扫描仪」
Build Scan 是 Gradle 官方提供的构建分析工具。它能把一次构建的完整信息记录下来,生成一个可交互的网页报告。我个人习惯在遇到构建性能问题时,第一件事就是跑一个 Build Scan。
1.1 启用 Build Scan
在 Gradle 6.0 以上版本,启用非常简单。你只需要在 settings.gradle.kts 里加一行插件声明:
// settings.gradle.kts
plugins {
id("com.gradle.enterprise") version "3.15.1"
}
gradleEnterprise {
server = "https://your-server.com" // 企业版需要配置服务器地址
}
如果你只是个人使用,不想搭服务器,可以用 Gradle 的免费云服务:
// 在命令行加参数
./gradlew build --scan
// 或者全局启用
// ~/.gradle/gradle.properties
org.gradle.scan.enabled=true
跑完构建后,终端会输出一个链接。点开它,你会看到一个类似这样的报告页面:
Build Scan 报告包含的核心信息:
- 构建耗时分布(每个 Task 花了多久)
- 缓存命中率(哪些 Task 被缓存了)
- 依赖下载时间(哪个仓库最慢)
- 配置阶段耗时(Settings 和 BuildScript 执行时间)
- 测试执行详情(每个测试用例的耗时)
我曾经在一个遗留项目上跑了一次 Build Scan,发现一个叫 processResources 的任务居然占了总构建时间的 40%。点进去一看,好家伙,它在每次构建时都在重新处理几百个模板文件。这就是典型的「看不见的浪费」。
1.2 如何阅读 Build Scan
打开报告后,我一般按这个顺序看:
- Performance 标签页:看总耗时和各个阶段的耗时占比
- Timeline 标签页:看 Task 的执行顺序,有没有串行执行的瓶颈
- Cache 标签页:看缓存命中率,低于 80% 就要警惕了
- Dependencies 标签页:看依赖下载耗时,有没有慢到离谱的仓库
小技巧:Build Scan 支持对比两次构建。你可以先跑一次「未优化」的构建,优化后再跑一次,直接看差异。我经常用这个功能来验证优化效果。
二、Profiling:定位性能瓶颈
Build Scan 能告诉你「哪里慢」,但有时候你需要更细粒度的信息。这时候就要上 Profiling 了。
2.1 使用 Gradle Profiler
Gradle 官方提供了一个叫 gradle-profiler 的工具。它能模拟多次构建,生成详细的性能报告。
安装方式:
// 通过 SDKMAN 安装
sdk install gradleprofiler
// 或者直接下载 jar 包
java -jar gradle-profiler-0.19.0.jar
使用示例:
// 对 build 任务进行 10 次基准测试
gradle-profiler --benchmark --project-dir /path/to/project build
// 输出结果在 /path/to/project/profile-out/
它会生成一个 HTML 报告,包含每次构建的耗时、GC 情况、CPU 使用率等。我个人觉得最有用的部分是 Task 耗时分布图,它能让你一眼看出哪些 Task 是「钉子户」。
2.2 手动 Profiling 技巧
有时候你不需要那么复杂的工具。几个简单的 Gradle 参数就能帮你定位问题:
// 查看每个 Task 的执行时间
./gradlew build --profile
// 输出在 build/reports/profile/ 下
这个 --profile 参数会生成一个 HTML 报告,列出每个 Task 的耗时、启动时间、结束时间。我经常用它来快速排查「哪个 Task 突然变慢了」。
注意:--profile 报告只在构建成功时生成。如果构建失败,你需要先修复错误再跑。
三、构建优化技巧
好了,现在你知道问题在哪了。接下来就是动手优化。我总结了几个最实用的技巧,都是我在实际项目中验证过的。
3.1 开启构建缓存
这是性价比最高的优化手段。Gradle 的构建缓存可以复用之前构建的输出,避免重复执行相同的 Task。
// settings.gradle.kts
buildCache {
local {
isEnabled = true
directory = File(rootDir, ".gradle-build-cache")
removeUnusedEntriesAfterDays = 30
}
// 如果有远程缓存服务器
remote {
url = uri("https://cache.example.com/")
isPush = true
}
}
开启后,你会发现第二次构建快很多。但要注意:不是所有 Task 都适合缓存。比如生成随机数的 Task,缓存了反而有问题。
3.2 并行执行与 Worker API
Gradle 默认是串行执行 Task 的。如果你的机器有多个 CPU 核心,完全可以并行跑。
// gradle.properties
org.gradle.parallel=true
org.gradle.workers.max=4
对于自定义 Task,可以用 Worker API 来实现内部并行:
// build.gradle.kts
abstract class MyTransformTask : DefaultTask() {
@get:InputFiles
abstract val inputFiles: ConfigurableFileCollection
@get:OutputDirectory
abstract val outputDir: DirectoryProperty
@get:Inject
abstract val workerExecutor: WorkerExecutor
@TaskAction
fun transform() {
workerExecutor.noIsolation().submit(TransformWorkAction::class.java) {
inputFiles.set(this@MyTransformTask.inputFiles)
outputDir.set(this@MyTransformTask.outputDir)
}
}
}
abstract class TransformWorkAction : WorkAction {
override fun execute() {
// 实际处理逻辑
}
}
我在一个处理图片资源的项目里用过这个 API,构建时间从 3 分钟降到了 45 秒。说白了,就是把一个大任务拆成多个小任务,同时跑。
3.3 依赖优化
依赖管理是构建性能的隐形杀手。我见过太多项目把整个 Spring Boot 全家桶都引进来,结果只用了其中两个模块。
| 优化点 | 说明 | 效果 |
|---|---|---|
| 按需引入依赖 | 只引入需要的模块,不要用 * |
减少依赖解析时间 |
| 使用 BOM 管理版本 | 统一版本号,避免冲突 | 减少版本冲突导致的重新解析 |
| 配置仓库镜像 | 用阿里云、华为云等国内镜像 | 大幅减少下载时间 |
| 开启依赖缓存 | Gradle 默认会缓存依赖 | 避免重复下载 |
// 配置国内镜像仓库
repositories {
maven("https://maven.aliyun.com/repository/public")
maven("https://maven.aliyun.com/repository/central")
mavenCentral()
}
3.4 配置阶段优化
Gradle 的构建分为三个阶段:初始化、配置、执行。配置阶段经常被忽略,但它可能是性能瓶颈。
我曾经在一个项目里发现,配置阶段居然占了总时间的 30%。原因是 build.gradle.kts 里有一堆 allprojects 和 subprojects 的配置,每次构建都要执行一遍。
优化方法:
- 避免在配置阶段执行耗时操作:比如文件 I/O、网络请求
- 使用
afterEvaluate延迟配置:只在需要的时候才执行 - 减少不必要的 Project 遍历:能用
configure就别用allprojects
// 不好的写法:配置阶段执行文件操作
allprojects {
val file = file("${rootDir}/config/${name}.properties")
if (file.exists()) {
// 每次构建都读文件
}
}
// 好的写法:延迟到执行阶段
allprojects {
afterEvaluate {
val file = file("${rootDir}/config/${name}.properties")
if (file.exists()) {
// 只在需要时执行
}
}
}
四、知识体系总览
下面这张图总结了本章的核心内容。你可以把它当作一个检查清单,每次优化构建时对照着看:
五、避坑指南
最后分享几个我踩过的坑:
我曾经…… 在一个多模块项目里,为了追求「极致性能」,把所有 Task 都设成了并行执行。结果呢?构建时间没降多少,反而因为资源竞争导致频繁 OOM。后来我才明白,并行不是万能的,要根据机器配置和 Task 类型来调整。
另一个教训:构建缓存虽然好,但要注意缓存失效策略。我见过有人把缓存时间设成 365 天,结果代码改了,缓存还在用旧的输出。建议本地缓存设 30 天,远程缓存设 7 天。
嗯,构建优化这件事,说白了就是「先测量,再优化」。不要凭感觉去改配置,用数据说话。Build Scan 和 Profiling 就是你的眼睛,优化技巧就是你的工具。用好它们,你的构建速度一定能上一个台阶。