为什么 GraalVM CE 的吞吐量小于 GraalVM EE 或 OpenJDK 8
Why GraalVM CE has smaller throughput than GraalVM EE or OpenJDK 8
我已经为使用动态规划找出最长公共子序列的方法创建了一个基准:
@Benchmark
def longestCommonSubsequenceDP(): String = {
val s1 = "Pellentesque lacinia"
val s2 = "Mauris purus massa"
val up = 1
val left = 2
val charMatched = 3
val s1Length = s1.length()
val s2Length = s2.length()
val lcsLengths = Array.fill[Int](s1Length + 1, s2Length + 1)(0)
for (i <- 0 until s1Length) {
for (j <- 0 until s2Length) {
if (s1.charAt(i) == s2.charAt(j)) {
lcsLengths(i + 1)(j + 1) = lcsLengths(i)(j) + 1
} else {
if (lcsLengths(i)(j + 1) >= lcsLengths(i + 1)(j)) {
lcsLengths(i + 1)(j + 1) = lcsLengths(i)(j + 1)
} else {
lcsLengths(i + 1)(j + 1) = lcsLengths(i + 1)(j)
}
}
}
}
val subSeq = new StringBuilder()
var s1Pos = s1Length
var s2Pos = s2Length
do {
if (lcsLengths(s1Pos)(s2Pos) == lcsLengths(s1Pos -1)(s2Pos)) {
s1Pos -= 1
} else if (lcsLengths(s1Pos)(s2Pos) == lcsLengths(s1Pos)(s2Pos - 1)) {
s2Pos -= 1
} else {
assert(s1.charAt(s1Pos - 1) == s2.charAt(s2Pos - 1))
subSeq += s1.charAt(s1Pos - 1)
s1Pos -= 1
s2Pos -= 1
}
} while (s1Pos > 0 && s2Pos > 0)
subSeq.toString.reverse
}
和运行它具有以下配置jmh:run -i 10 -wi 10 -f1 -t1
并得到以下结果:
GraalVM EE 1.0.0-rc10
[info] Benchmark Mode Cnt Score Error Units
[info] LCS.longestCommonSubsequenceDP thrpt 25 91.411 ± 4.355 ops/ms
GraalVM CE 1.0.0-rc10
[info] Benchmark Mode Cnt Score Error Units
[info] LCS.longestCommonSubsequenceDP thrpt 25 26.741 ± 0.408 ops/ms
OpenJDK 1.8.0_192
[info] Benchmark Mode Cnt Score Error Units
[info] LCS.longestCommonSubsequenceDP thrpt 25 45.216 ± 1.956 ops/ms
我还做了另一个测试,我创建了一个包含数千个对象的列表,对其进行了一些过滤和排序,thrpt
在 GraalVM CE 上是最小的。
为什么会有这种差异?
您得到不同的结果是因为您使用的运行时启用了不同的顶级 JIT 编译器。
除非另有说明(例如使用命令标志):
- OpenJDK 1.8.0_192 使用 C2
- GraalVM CE 1.0.0-rc10 使用 Graal 编译器。
- GraalVM EE 1.0.0-rc10使用企业版Graal编译器。
JIT 在运行时将您的代码编译为机器代码,这在很大程度上取决于原始代码、工作负载、JIT 配置、启用的优化等。
可以合理地预期 JIT 编译器的不同实现会在同一基准测试中显示不同的结果。
如果您问的是为什么 GraalVM CE 在这个特定的基准测试中没有显示出更好的结果,而是关于一般差异的哲学问题;这是一个简短的解释。
所有编译器都擅长某事,例如 Graal 具有出色的转义分析和内联算法,这在使用抽象的代码上显示了很好的结果:分配对象、调用方法等。
这个特定的基准测试用整数填充一个数组并运行一个循环。这可能并不完全允许 Graal 做它擅长的事情。
所以,这是 C2 更擅长的微基准测试的一个例子。您可能可以构建一个类似的基准测试,GraalVM CE 将显示出优于 OpenJDK 的性能(也许您可以尝试这个:http://www.graalvm.org/docs/examples/java-simple-stream-benchmark/)。
GraalVM 团队运行了大量的基准测试,这就是 GraalVM CE 更好的知识来源。但是,需要了解的是,将一组复杂的基准测试结果简化为一个数字对于评估任何特定代码段及其工作负载的性能而言并不是最有意义的事情。人们应该始终努力评估他们的代码。
我已经为使用动态规划找出最长公共子序列的方法创建了一个基准:
@Benchmark
def longestCommonSubsequenceDP(): String = {
val s1 = "Pellentesque lacinia"
val s2 = "Mauris purus massa"
val up = 1
val left = 2
val charMatched = 3
val s1Length = s1.length()
val s2Length = s2.length()
val lcsLengths = Array.fill[Int](s1Length + 1, s2Length + 1)(0)
for (i <- 0 until s1Length) {
for (j <- 0 until s2Length) {
if (s1.charAt(i) == s2.charAt(j)) {
lcsLengths(i + 1)(j + 1) = lcsLengths(i)(j) + 1
} else {
if (lcsLengths(i)(j + 1) >= lcsLengths(i + 1)(j)) {
lcsLengths(i + 1)(j + 1) = lcsLengths(i)(j + 1)
} else {
lcsLengths(i + 1)(j + 1) = lcsLengths(i + 1)(j)
}
}
}
}
val subSeq = new StringBuilder()
var s1Pos = s1Length
var s2Pos = s2Length
do {
if (lcsLengths(s1Pos)(s2Pos) == lcsLengths(s1Pos -1)(s2Pos)) {
s1Pos -= 1
} else if (lcsLengths(s1Pos)(s2Pos) == lcsLengths(s1Pos)(s2Pos - 1)) {
s2Pos -= 1
} else {
assert(s1.charAt(s1Pos - 1) == s2.charAt(s2Pos - 1))
subSeq += s1.charAt(s1Pos - 1)
s1Pos -= 1
s2Pos -= 1
}
} while (s1Pos > 0 && s2Pos > 0)
subSeq.toString.reverse
}
和运行它具有以下配置jmh:run -i 10 -wi 10 -f1 -t1
并得到以下结果:
GraalVM EE 1.0.0-rc10
[info] Benchmark Mode Cnt Score Error Units
[info] LCS.longestCommonSubsequenceDP thrpt 25 91.411 ± 4.355 ops/ms
GraalVM CE 1.0.0-rc10
[info] Benchmark Mode Cnt Score Error Units
[info] LCS.longestCommonSubsequenceDP thrpt 25 26.741 ± 0.408 ops/ms
OpenJDK 1.8.0_192
[info] Benchmark Mode Cnt Score Error Units
[info] LCS.longestCommonSubsequenceDP thrpt 25 45.216 ± 1.956 ops/ms
我还做了另一个测试,我创建了一个包含数千个对象的列表,对其进行了一些过滤和排序,thrpt
在 GraalVM CE 上是最小的。
为什么会有这种差异?
您得到不同的结果是因为您使用的运行时启用了不同的顶级 JIT 编译器。 除非另有说明(例如使用命令标志):
- OpenJDK 1.8.0_192 使用 C2
- GraalVM CE 1.0.0-rc10 使用 Graal 编译器。
- GraalVM EE 1.0.0-rc10使用企业版Graal编译器。
JIT 在运行时将您的代码编译为机器代码,这在很大程度上取决于原始代码、工作负载、JIT 配置、启用的优化等。
可以合理地预期 JIT 编译器的不同实现会在同一基准测试中显示不同的结果。
如果您问的是为什么 GraalVM CE 在这个特定的基准测试中没有显示出更好的结果,而是关于一般差异的哲学问题;这是一个简短的解释。 所有编译器都擅长某事,例如 Graal 具有出色的转义分析和内联算法,这在使用抽象的代码上显示了很好的结果:分配对象、调用方法等。
这个特定的基准测试用整数填充一个数组并运行一个循环。这可能并不完全允许 Graal 做它擅长的事情。 所以,这是 C2 更擅长的微基准测试的一个例子。您可能可以构建一个类似的基准测试,GraalVM CE 将显示出优于 OpenJDK 的性能(也许您可以尝试这个:http://www.graalvm.org/docs/examples/java-simple-stream-benchmark/)。
GraalVM 团队运行了大量的基准测试,这就是 GraalVM CE 更好的知识来源。但是,需要了解的是,将一组复杂的基准测试结果简化为一个数字对于评估任何特定代码段及其工作负载的性能而言并不是最有意义的事情。人们应该始终努力评估他们的代码。