第2章 构建性能分析工具:Gradle Profiler与Build Scan

说实话,做Gradle性能优化,最怕的就是瞎猜。

我以前接手过一个项目,构建慢得让人抓狂。团队里大家各执一词,有人说“是注解处理的问题”,有人说“是Dex任务的问题”。结果花了两周加各种缓存和并行,速度根本没变。为什么?因为没有数据支撑,全是拍脑袋。

后来我学了乖——先拿工具把瓶颈定位清楚,再动手。这期我就聊聊我最常用的两把“手术刀”:Gradle Profiler和Build Scan。它们一个负责微观采样,一个负责宏观诊断,配合起来基本能解决90%的性能迷雾。

2.1 Gradle Profiler:命令行里的“慢镜头”

Gradle Profiler是个独立工具,不是Gradle插件。它能在你本地反复执行构建,并精确记录每个任务的时间、GC停顿、甚至CPU采样。说白了,它就像给构建过程装了个慢动作回放。

安装与基本用法

我一般通过SDKMAN或直接下载JAR包安装。简单到不行:

# 安装
sdk install gradleprofiler

# 运行一次基准测试
gradle-profiler --benchmark --project-dir /path/to/project --scenario-file benchmark.scenarios

其中--scenario-file定义你要测什么任务。比如:

# benchmark.scenarios
clean_assemble {
    tasks = ["clean", "assembleDebug"]
    warm-ups = 2
    iterations = 5
}

它会先做2次热身(忽略第一次的JIT影响),再跑5轮取平均值。结果会生成HTML报告,任务耗时、GC次数、甚至线程争用都一目了然。

我个人的小习惯:把warm-ups设为3,iterations设为10。虽然慢一点,但数据更稳定,尤其是Kotlin编译这种有增量缓存的场景。

常用参数

参数作用
--benchmark启动基准测试模式,输出详细报告
--measure-config测量配置阶段(Gradle 7+推荐)
--profile-jvm同时采集JVM CPU采样(火焰图)
--csv输出CSV,方便导入Excel

我记得有一次,项目里AssembleDebug总是比别的机器慢3分钟。用Profiler一跑,发现有个自定义Task在每次构建都重新下载远程资源。嗯,问题找到了——原来是依赖声明没加cacheReloading。你看,没有工具的话,谁能猜到是网络I/O在作祟?

避坑指南:我曾经在Windows上跑Profiler,结果每次报告里的GC时间都异常高。后来发现是Windows Defender在扫描临时文件。建议跑Profiler前关闭实时扫描,或者把项目加入排除列表。

2.2 Build Scan:可视化诊断的“上帝视角”

如果说Profiler是显微镜,Build Scan就是卫星地图。它能把一次构建的完整生命周期——从配置到任务执行到缓存命中——变成一份在线网页,可交互、可分享。

如何启用

Build Scan是Gradle内置的功能(开源免费),你只需要在命令行加个参数:

./gradlew assembleDebug --scan

构建结束后,终端会输出一个链接:

Publishing build scan...
https://gradle.com/s/abc12345

点进去,你就看到了构建的全景图。对于团队协作,我建议在build.gradle里全局开启:

// settings.gradle
gradleEnterprise {
    buildScan {
        publishAlways()
        termsOfServiceUrl = "https://gradle.com/terms-of-service"
        termsOfServiceAgree = "yes"
    }
}

这样每次构建都会自动发布,但注意公司项目可能有安全风险,建议只在内网使用Gradle Enterprise。开源免费版Publish到Gradle公共服务器,我会勾选publishOnFailureIfRequested来避免不必要的曝光。

如何读懂Build Scan的核心数据

进入页面后,我重点看三个地方:

  1. Performance标签:展示构建总耗时、CPU使用率、GC活动。如果GC时间占比超过15%,就要考虑调JVM堆大小了。
  2. Task Execution标签:按耗时从大到小排序。直接找那些“胖任务”——通常是最耗时的前五个。我遇到过最夸张的一个项目,有个processResources任务因为开启了duplicatesStrategy导致每次全量拷贝,耗时41秒。
  3. Cache详情:Build Scan会告诉你哪些任务从缓存命中,哪些没命中。没命中的原因一般包括:输入文件改了、任务输出被删了、或者任务标记为outputs.upToDateWhen { false }。
我个人的诊断口诀:先看Performance看GC,再看Task看耗时,最后看Cache找原因。三步走,不出错。

2.3 双剑合璧:Profiler + Build Scan的实际攻防

这两个工具不是二选一,而是搭配使用。我一般走这个流程:

  • 先用Profiler做基准测试:固定场景跑多轮,排除随机波动。找到某个Task耗时异常或GC频繁。
  • 再用Build Scan深挖细节:点进具体任务,看它的输入输出、依赖链、缓存状态。尤其是那些“跨模块依赖”导致的连锁执行。

比如我最近优化一个大型微服务项目,Profiler报告显示compileKotlin总耗时68秒,但Build Scan里点进去发现,有6个模块的Kotlin编译是串行执行的,因为它们在同一个parallelThreads池里抢资源。后来通过调整org.gradle.parallel和任务依赖关系,把68秒降到21秒。

工具对比速查表

维度Gradle ProfilerBuild Scan
数据粒度微秒级采样,含JVM内部任务级,含缓存和依赖
多次运行原生支持基准,可统计方差单次构建,需手动对比
适用场景定位JVM层问题、验证优化前后差异日常诊断、团队共享、CI集成
输出形式本地HTML/CSV/火焰图在线可交互页面(链接)
网络要求无需网络需上传至Gradle服务(免费版)
构建性能分析工具使用流程 构建慢 / 怀疑瓶颈 选择分析工具 Gradle Profiler 基准测试 | 采样分析 | 火焰图 Build Scan 任务耗时 | 缓存命中 | 依赖图 定位具体瓶颈(任务/GC/配置) 实施优化 → 验证效果 💡 先Profiler后Scan

最后几个值得留意的点

  • 版本兼容:Profiler最新版支持Gradle 7+,但旧项目还在用Gradle 6.x的话,记得用Profiler 0.17或更低版本。Build Scan在Gradle 8.x里默认通过--scan可用,无需额外插件。
  • CI环境:如果只用Build Scan,建议在CI里加上--scan,然后在构建失败时自动发布scan链接。我曾在Jenkins里搞了个脚本,每次构建后把Scan链接贴到Slack上——团队查问题效率翻倍。
  • 不要只依赖一种工具:有一次我用Profiler看到某个Task耗时60秒,Build Scan却显示只用了30秒。原因?Profiler采样到了JVM的SafePoint停顿。这时需要两者印证,才能确定是任务本身慢还是JVM暂停的干扰。
小技巧:在本地开发时,我习惯开一个alias:alias gbp='gradle --scan --no-daemon'。这样跑一次就能拿到完整的Build Scan,而且禁用守护进程避免缓存数据污染对比结果。嗯,说起来简单,但很多人没注意到--no-daemon在这个场景下的重要性。