01 背景
Alibaba Dragonwell 作为 OpenJDK 的下游版本,是阿里巴巴针对 10万+ 服务器上运行的在线电商、金融、物流应用优化的 OpenJDK 实现。 阿里巴巴和 OpenJDK 社区紧密合作,将尽可能多的 Alibaba Dragonwell 定制功能带到上游。Dragonwell 是 OpenAnolis 的默认 JDK,Alibaba 的众多 Java 应用,正在逐步迁移到 Dragonwell 上。
Intel Java 团队长期致力于 OpenJDK 的发展,贡献了大量优化特性,这些工作使得 OpenJDK 可以更好利用现代 CPU 的强大特性。VectorAPI 是 Java 在特定领域进行高性能计算的利器,广大 Java 开发者通过 VectorAPI 可以精确地控制和利用现代 CPU 普遍存在的 SIMD (Single Instruction Multiple Data) 硬件单元,特定的应用可以获得数倍以上的性能提升。
02 Java 高性能计算现状
SIMD 计算单元在 X86 平台(非 X86 平台也普遍存在 SIMD)上从 MMX(Multi Media eXtension)时代,演进到当前的 AVX-512 (Advanced Vector Extensions)/AMX(Advanced Matrix Extensions)时代,SIMD 技术在多媒体处理,游戏娱乐,大数据处理和最近火热的 AI 领域都发挥了至关重要的作用。开发者可以利用 C/C++(intrinsic指令),汇编语言等语言工具显式的编写 SIMD 相关代码(称之为向量化编码)来利用 SIMD 单元;也可以利用编译器/解释器的自动向量化功能来进行。JVM(Java Virtual Machine)也具有自动向量化的能力。
在利用编译器/解释器的自动向量化功能的时候,虽然开发人员的负担较小,但由于这些工作都依赖于编译器/解释器的实现,很多时候无法达到预期的目的。而开发人员能做的却很有限,SIMD 硬件的性能往往不能充分发挥;而直接针对 SIMD 硬件编程,较老版本的 Java 却只能通过 JNI(Java Native Interface),调用使用 C/C++ 或者汇编语言实现的库来实现。JNI 的引入,将带来不容忽视的额外性能开销;同时混合编程模式也会增加系统管理维护的复杂度。Java VectorAPI (向量 API)的出现,给了 Java 开发者直接面向 SIMD 硬件的编程能力。使用 SIMD 硬件的途径:
自动向量化 | 调用本地语言库 | 直接针对 SIMD 单元编程 | |
C/C++、汇编等本地语言 | 是 | 是 | 是 (Intrinsic/汇编) |
Java | 是 | 是(JNI) | 是(VectorAPI) |
03 VectorAPI 介绍
VectorAPI (Incubator, JEP 338, JEP:JDK Enhancement Proposals) 最初于 2018 年 4 月开始创建,在 OpenJDK16 作为孵化器项目(2020年10月)被引入。随着后续 OpenJDK 版本的升级,VectorAPI 也得到了同步升级:
- OpenJDK 17 -> JEP 414,Second Incubator
- OpenJDK 18 -> JEP 417,Third Incubator
- OpenJDK 19 -> JEP 426,Fourth Incubator
- OpenJDK 20 -> JEP 438,Fifth Incubator
每次 VectorAPI 的升级都会带来性能提升,更多功能和 Bugfix。VectorAPI 的编程使用的是纯 Java 代码,下面看一个简单例子:
// 传统写法实现2个数组相加
void add (float[] A, float[] B, float[] C) {
for (int i = 0; i < C.length; i++) {
C[i] = A[i] + B[i];
}
}
// 使用VectorAPI的2个数组相加
public class AddClass<S extends Vector.Shape<Vector<?, ?>>> {
private final FloatVector.FloatSpecies<S> spec;
AddClass (FloatVector.FloatSpecies<S> v) {spec = v; }
// vector routine for add
void add (float[] A, float[] B, float[] C) {
int i=0;
for (; i + spec.length() < C.length; i += spec.length()) {
FloatVector<S> av = spec.fromArray(A, i);
FloatVector<S> bv = spec.fromArray(B, i);
av.add (bv).intoArray(C, i);
}
// clean up loop
for (; i < a.length; i++) C[i] = A[i] + B[i];
}
}
可以看出,在使用 VectorAPI 后,在支持 AVX-512 的硬件平台上,一次加法可以处理 (512/32=16) 16 个浮点数;而传统加法一次只能处理一个浮点数。下面是一些 VectorAPI 的实际例子:
- 高性能计算、AI、多媒体领域广泛使用的 BLAS (Basic Linear Algebra Subprograms,基本线性代数子程序),可以得到 2.2X~4.5X 的提升。
- 图像处理 Sepia 过滤 ,最多得到 6 倍提升:
- 数据库应用 2 倍以上提升:
- Parquet-Decoder 有 4~8 倍提升:https://github.com/apache/parquet-mr/pull/1011
由于 VectorAPI 是一个比较新的模块,更多利用 VectorAPI 的新项目正在开发中。
04 业界 Java 版本现状
在 Apache Parquet-mr 项目中,使用 VectorAPI 需要 JDK17 的支持。一方面 JDK17 是 Java 的 LTS (Long-Term Support) 版本,更为重要的是前一个 Java LTS 版本 JDK11 并不支持 VectorAPI。但是业界 JDK11 的使用比 JDK17 更为广泛,而在成熟的生产环境中升级 Java 版本是一件代价高昂的事情,这就造成了 VectorAPI 在业界推广使用的一个显而易见的巨大障碍。对此业界也做了不少努力和尝试,比如阿里巴巴在其内部使用的 AJDK上,已经加入了 VectorAPI 的支持,但是其实现和 OpenJDK 社区还是有差别,后续的升级维护不是一件容易的事情。因此阿里巴巴和英特尔开始了将 VectorAPI (JEP 338)移植到 Dragonwell11 上的项目。这样既可以利用 VectorAPI 的强大功能,又保护了现在的投资,避免了升级 JDK 的风险。
05 移植难点
- 涉及代码量巨大
社区 JEP 338 涉及 336 文件修改,涉及代码行数 29 万行。而这些都是以 JDK16 为基准的和基于 JDK11 的 Dragonwell 相比,差异会更大。
- 保持和上游 OpenJDK 的关联
而且一方面要让 Dragonwell11 能继续方便追踪上游 OpenJDK11 的修正增强,还要让 VectorAPI 也能较为方便跟踪上游 OpenJDK 后续 VectorAPI 的演进,对移植工作带来了不小挑战。
- 性能要和上游 OpenJDK 接近
OpenJDK11 以后的版本的一些性能相关改动以及如何引入 Dragonwell11?
- 稳定可靠的强需求
Dragonwell 需要支持现有的大量业务,稳定可靠是第一位的要求。
06 解决方案
针对海量的代码,阿里巴巴和英特尔都投入了大量资源,其中双方都包括了上游 OpenJDK 社区 VectorAPI 的关键贡献者,双方紧密合作,讨论方案,检查代码。在移植过程中,尽量保持 Dragonwell11 的原有结构,只涉及 VectorAPI 的部分才采用上游的实现。一些上游 JDK11 以后的改动,分析其实现,如果可以使用现有 Dragonwell11 的组件,就不引入额外的改动。这样把对 Dragonwell11 的影响降到最小。并且利用 OpenJDK 内建的测试集,完整覆盖移植后的 Dragonwell11,来保证移植的质量。
07 Dragonwell11 + VectorAPI
目前,VectorAPI 已经合并到 Dragonwell11 的主分支(master),且完全兼容 VectorAPI 1st Incubator (JEP 338),后续还会把 JEP 414、JEP 417、JEP 426、JEP 438 的功能移植到 Dragonwel11 上。
相关链接:
Alibaba Dragonwell:https://github.com/alibaba/dragonwell11
OpenJDK :https://github.com/openjdk/jdk
文/朱文杰、金钟晖
本文为阿里云原创内容,未经允许不得转载。