每一个性能优化工程师的苦恼,莫过于练就了一身“绝世武功”,却苦于找不到真正的敌人——性能瓶颈。现实中,大部分时间往往耗费在定位瓶颈和构建合适的性能测试上,而一旦找准问题,修复反而常常迅速完成。因此,精准识别性能瓶颈,才是高效性能优化的核心痛点。
那么,AI 能否帮助我们自动寻找这些隐藏的瓶颈,并指导实施精准的性能提升?带着这一问题,我们开发了一个通用的 AI 驱动性能分析系统,并以 Apache Spark 作为典型案例进行验证。评估显示方法是有效的:AI 发现的可优化函数,通过 Dragonwell 的 Native 加速技术应用后,在 JMH 测试中最高实现了 10 倍的性能提升;在 TPC-DS 端到端测试中,结合原有优化使整体性能最高提升了 9.69%。Spark 的 Native 优化函数已经发布在最新的 Dragonwell 版本中。
性能瓶颈分析的传统流程及其挑战
目前,开发者通常遵循以下三步流程来分析性能瓶颈:
- 采集热点数据:在 Java 项目中,常用 AsyncProfiler 等工具采集运行时数据,生成火焰图和 JFR(Java Flight Recorder)格式的二进制文件。开发者依赖经验,从火焰图中识别潜在的瓶颈函数。
- 代码审查:回到源代码中,检查该函数为何执行耗时,是否存在优化空间。
- 验证或回退:若发现优化点,则实施并验证;否则,返回火焰图寻找下一个候选。
这一流程高度依赖人工经验,且极其耗时。于是我们不禁思考:能否将火焰图数据与源代码直接交给 AI,让它自动筛选出值得优化的候选函数?
然而,要评估 AI 是否胜任,必须先看清问题的规模。以 Spark 4.0 运行 TPC-DS 基准测试为例,优化分析面临的问题空间极为庞大:
- 2254 个 JFR 火焰图文件:TPC-DS 包含 103 个查询,在 3 个节点上运行,每个节点启动多个进程,每个进程生成一个 JFR 文件,总计为 2254 个,每个文件包含约 8 万个函数采样点。
- 28 万行代码:Spark 4.0 代码库包含约 27 万行 Scala 和 1 万行 Java 代码,需从中定位瓶颈函数及其上下文。
- 函数优化潜力判断:对每个候选函数,需要判断是否具备实际优化价值。
在这个复杂的问题空间中,第三项任务——判断函数是否可优化——正是 AI 的强项;第二项(理解代码上下文)AI 也能较好应对;但第一项(从海量二进制数据中提取关键信息)则远非当前大模型所长。因此,我们必须设计一个扬长避短的工作流,让 AI 专注于它最擅长的部分,同时借助传统工具处理结构化、高吞吐的数据处理任务。
工作流设计:传统工具与 AI 的协同架构
为此,我们构建了如下图所示的智能性能分析优化系统。系统以任意 Java/Scala 项目的代码仓库和 JFR 文件目录为输入,输出一份结构化的优化分析报告。
该系统的核心是一确定性强、流转清晰的分析工作流。我们选择用程序而非 Skill 来实现这一流程,原因在于:每一步的触发条件明确、逻辑固定,使用代码实现不仅更快、更稳定,还能避免不必要的大模型 token 消耗。
工具的强项:快速、稳定、可靠
AI 虽强大,但并非万能。只有与传统工具深度协作,才能使其聚焦核心任务,发挥最大效能。为此,我们开发了两个关键协作工具供 AI 调用:
- JFR 热点分析工具:将成百上千个 JFR 文件聚合,生成全局热度摘要,提炼出 Top N 热点函数列表,使大模型无需处理原始二进制数据。
- 基于 AST 的函数查询工具:通过解析源代码构建抽象语法树(AST),精准定位并返回目标函数的完整定义及上下文,相比简单的 grep 搜索更准确、更健壮。
这两个工具有效屏蔽了 AI 不擅长的基础数据处理和模糊文本匹配问题,减少了 AI 工作时的上下文数量,使其能够保持在优化分析上关注度。这种“各司其职”的协作理念贯穿整个系统设计。在初期,我们曾尝试将所有任务交由 AI 完成,但很快发现其在某些结构化任务上表现不稳定。例如,判断一个函数是否为 synchronized 时,AI 会错误地在文件中全局搜索关键字,导致误判。于是我们将此类任务剥离出来,用确定性代码实现,显著提升了系统的准确性和可靠性。
AI 的强项:语义理解与意图识别-以 Native 加速优化为例
Dragonwell 21 中引入了一项名为“Native 加速优化”的技术,可使特定函数性能提升高达 10 倍。其原理是绕过 JVM 的部分开销,直接调用高度优化的本地汇编实现。但这也带来了严格的安全约束,只有满足特定条件的函数才能安全启用该优化,典型的如:
- 无逃逸:不能通过返回值或设置全局变量等方式将内部新创建的实例逃逸出函数的作用范围,这样会导致 Native 内存与 JVM 内存之间的复杂交互,是 Native 加速优化没有处理的。
- 无异常分析:函数不能抛出异常,Native 加速优化没有实现异常处理机制。
在安全约束之外,函数中也需要存在优化机会,才具备实施的必要性,如:
- 函数的汇编代码不能过少:如果函数的汇编代码指令非常少,那么 JDK 的 JIT 编译的代码就足够好了。
- 函数是计算密集型:计算密集型的编译优化空间较大,IO 操作、对象分配操作等没有优化空间。
- 存在其他优化机会:例如消除条件跳转、采用向量指令等保持函数输入输出不变,但是采用更快的语义优化的机会。
传统的静态分析很难实现以上的安全性和必要性检查,而依靠人工执行这些检查更是如同大海捞针,但是对这几点的分析却正是大模型的强项。我们为此设计了三个专用 AI Agent:
- 逃逸分析 Agent:相比复杂的传统指针分析,大模型能基于代码语义快速判断对象是否逃逸,实现简单且效果良好。
- 异常分析 Agent:异常分析的难点并不在于检测代码中是否存在显式的 throw,而是判断抛异常的行为是否位于不可达的路径上,使得事实上不会抛出异常。比如以下代码中第 15 行的 getSize 函数是否会抛出异常呢?从表面上看,16 行的 switch 语句的 default 情况就会抛出异常。但是仔细分析可以看到,在生产环境中,第 16 行的 getUaoSize 只可能返回 4 或者 8,而不可能是别的值,所以 default 情况是不可达的。传统的静态分析很难分析出这段代码的意图,但是大模型可以理解代码意图,并且从上下文中分析得到不会抛出异常的答案。
private staticfinalint UAO_SIZE = Platform.unaligned() ? 4 : 8;
privatestaticint TEST_UAO_SIZE = 0;
// used for test only
public static void setUaoSize(int size) {
assert size == 0 || size == 4 || size == 8;
TEST_UAO_SIZE = size;
}
public static int getUaoSize() {
return TEST_UAO_SIZE == 0 ? UAO_SIZE : TEST_UAO_SIZE;
}
public static int getSize(Object object, long offset) {
returnswitch (getUaoSize()) {
case4 -> Platform.getInt(object, offset);
case8 -> (int) Platform.getLong(object, offset);
default ->
// checkstyle.off: RegexpSinglelineJava
thrownew AssertionError("Illegal UAO_SIZE");
// checkstyle.on: RegexpSinglelineJava
};
}
- 优化机会分析 Agent:大模型不仅能够发现优化机会,还能够联系上下文,挖掘出人类容易忽略的问题。例如对于以下函数,我们曾经也人工看过,但并没有仔细观察过第 1 到 20 行的数组。因为面对这么复杂的数据,人的本能是逃避跳过。但是大模型告诉我们,只要把这个数组中的 0 换成 1,就可以将第 28 行的三元条件判断操作变成直接读数组中的元素,从而实现优化。更进一步将 byte 数组改为 int 数组的话,还能节省从 byte 到 int 的符号扩展操作开销。
private staticbyte[] bytesOfCodePointInUTF8 = {
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 0x00..0x0F
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 0x10..0x1F
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 0x20..0x2F
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 0x30..0x3F
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 0x40..0x4F
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 0x50..0x5F
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 0x60..0x6F
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 0x70..0x7F
// Continuation bytes cannot appear as the first byte
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 0x80..0x8F
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 0x90..0x9F
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 0xA0..0xAF
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 0xB0..0xBF
0, 0, // 0xC0..0xC1 - disallowed in UTF-8
2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, // 0xC2..0xCF
2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, // 0xD0..0xDF
3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, // 0xE0..0xEF
4, 4, 4, 4, 4, // 0xF0..0xF4
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0// 0xF5..0xFF - disallowed in UTF-8
};
...
public static int numBytesForFirstByte(final byte b) {
finalint offset = b & 0xFF;
byte numBytes = bytesOfCodePointInUTF8[offset];
return (numBytes == 0) ? 1: numBytes; // Skip the first byte disallowed in UTF-8
}
通过这些 AI Agent 的协作工作,系统能够高效、准确地评估函数是否适合应用 Native 加速优化。
效果:从函数到端到端的显著提升
性能优化分析系统以 Spark 代码仓和 2000+ 的 JFR 文件为输入,对最热的 400 个函数(200 个 Spark 函数,200 个 JDK 函数)进行分析,耗时 3 个小时 21 分钟,成功识别出 34 个具有 Native 加速优化潜力的函数。
出于开发成本考虑,我们选取了其中的两个典型函数进行优化,在 Dragonwell 21 上的测试效果如下:
| 函数 | 线程热度 | 进程热度 | 全局热度 | JMH 测试单函数优化 |
|---|---|---|---|---|
| org.apache.spark.unsafe.map.BytesToBytesMap.safeLookup(Ljava/lang/Object;JILorg/apache/spark/unsafe/map/BytesToBytesMap$Location;I)V | 13.71% | 12.66% | 2.98% | 59.74% |
| org.apache.spark.unsafe.types.UTF8String.numChars()I | 8.77% | 7.59% | 1.08% | 1063% |
线程热度:在单个线程中的执行热度。有的函数全局热度很高,但是线程热度低,说明函数执行时间被大量的线程并行稀释。这种函数不是性能瓶颈。进程热度:在单个进程中的热度,对于 TPCDS 测试就是单个 query 中的热度。全局热度:将所有 JFR 文件合并起来后的全局热度,对于 TPCDS 测试就是在全部 query 整体中的热度。在 TPC-DS 全部 query 的端到端测试中,优化成果显著:
- Dragonwell 21 + Native 加速优化相比 OpenJDK 21 提升 5.7%,其中 AI 发现优化贡献约2.2%。
- Dragonwell 11 + Native 加速优化 相比 OpenJDK 11 提升 9.69%,其中 AI 发现优化贡献约4%。
- Dragonwell 8 + Native 加速优化 相比 OpenJDK 8 提升 6.11%,其中 AI 发现优化贡献约 2.5%。
端到端测试环境为:
- 节点:一个 master,三个 worker。
- CPU:AMD EPYC 9T24,master 8 核,worker 32 核。
- 内存:master 32G,worker 128G。
下一步:深化 AI 能力与自动化闭环
基于当前成果,我们计划从以下方向持续推进:
- 拓展优化分析维度:开发更多类型的 AI Agent,覆盖更广泛的优化场景。例如,对比不同厂商的 JDK 在相同负载下的性能差异,为 Dragonwell 自动发现编译优化机会;探索限制条件更宽松的 Java 代码优化机会,只需修改 Java 代码即可实现优化。
- 构建端到端自动化流水线:将性能采样、热点识别、AI 分析、优化建议生成、自动打补丁、回归测试与效果验证等环节串联起来,形成“分析-优化-验证”闭环。未来目标是实现“一键性能调优”,大幅降低人工介入成本。
总结
本文实践揭示了两个关键洞见:
第一,AI 与传统工具与协同而非替代。 性能优化是一个复杂系统工程,既有结构化、高吞吐的数据处理任务(如 JFR 聚合、AST 解析),也有高度依赖语义理解的判断任务(如逃逸分析、异常可达性)。将前者交由高效稳定的传统工具处理,后者交由 AI 专注分析,才能实现效率与准确性的双赢。这不是“把所有事情扔给 AI”,而是“让每个组件做它最擅长的事”。第二,AI 在性能优化分析中具有良好效果。 无论是识别 Native 加速机会,还是挖掘隐藏的代码优化点,AI 都展现出超越人工的洞察力。当前系统虽已取得显著收益,但仍有大量手动环节。下一步应着力打通全流程自动化,让 AI 驱动的性能优化从“辅助工具”进化为“标准基础设施”。在 AI 的辅助下,Dragonwell 21 所特有的优化能力得到了放大,将优化范围扩展到了原先依靠人力难以抵达的位置,取得了更好的优化效果。文中介绍的 Spark Native 优化函数已经发布在了最新的 Dragonwell21、11 和 8 的版本中(Alibaba_Dragonwell_Extended_21.0.10.0.10+7、Alibaba_Dragonwell_Extended_11.0.30.27.7、Alibaba_Dragonwell_Extended_8.28.27),具体用法请参考:github.com/dragonwell-…