【发布时间】:2016-05-16 14:02:41
【问题描述】:
我正在测试两种不同的方法(primes() 和 primesOpt())来使用 Java 8 IntStream 收集前 N 个素数。我从Java 8 in Action 的第 6 章中获取了这些示例。您可以从这个 gist Primes.java 和这个 pom.xml 获取源代码,以使用 Maven 和 JMH 集成构建它。 (您可以将pom.xml 复制到项目文件夹并将Primes.java 复制到src\main\java\primes 并使用以下命令构建它:mvn clean install。之后您可以使用:java -jar target\benchmarks.jar 运行基准测试。
第一个例子(primes() 方法)是一个简单的算法,可以将 N 个素数收集到一个List<Integer> 中。第二个(primesOpt() 方法)是一种增强方法,它只测试以前素数的除法。
我用 JMH 测试了这两种实现,以计算质数的 List<Integer> 到最大值 10,000:
@Benchmark
public int testPrimes() {
return primes(10_000).size();
}
@Benchmark
public int testPrimesOpt() {
return primesOpt(10_000).size();
}
根据 JVM 架构,我得到了不同的加速。在 64 位 JVM 中,我观察到 primesOpt() 比标准版本 primes() 加速了 25%,而 JVM 32 位则没有加速。
JRE 1.8.0_91-b14 64 位的结果:
Benchmark Mode Cnt Score Error Units
Primes.testPrimes thrpt 50 269,278 ± 15,922 ops/s
Primes.testPrimesOpt thrpt 50 341,861 ± 25,413 ops/s
JRE 1.8.0_91-b14 32 位的结果:
Benchmark Mode Cnt Score Error Units
Primes.testPrimes thrpt 200 105,388 ± 2,741 ops/s
Primes.testPrimesOpt thrpt 200 103,015 ± 2,035 ops/s
这些测试是在具有双核 Intel I7 Cpu、超线程的机器上执行的,产生 2 个内核和 4 个硬件线程。此外,该系统具有 4GB 的 RAM。使用的 JVM 版本是 1.8.0_91-b14,在 Windows 7 上运行。基准测试以 1024MB 的最小堆大小执行(对应于-Xms1024M)。在测量期间没有其他活动在运行。
您知道为什么我不能在 JVM 32 位上观察到素数算法优化版本的相同性能改进吗?
primes()方法实现:
public static boolean isPrime(int n) {
int root = (int) Math.sqrt(n);
return IntStream
.rangeClosed(2, root)
.noneMatch(div -> n%div == 0);
}
public static List<Integer> primes(int max) {
return IntStream
.range(2, max)
.filter(Primes::isPrime)
.collect(ArrayList::new, ArrayList::add, ArrayList::addAll);
}
primesOpt()方法实现:
public static boolean isPrimeOpt(List<Integer> primes, int n) {
int root = (int) Math.sqrt(n);
return takeWhile(primes, root)
.stream()
.noneMatch(div -> n%div == 0);
}
public static List<Integer> takeWhile(List<Integer> src, int max) {
int i;
for(i = 0; i < src.size() && src.get(i) <= max; i++) {}
return src.subList(0, i);
}
public static List<Integer> primesOpt(int max) {
ArrayList<Integer> res = new ArrayList<>();
return IntStream
.range(2, max)
.filter(n -> Primes.isPrimeOpt(res, n))
.collect(() -> res, ArrayList::add, (l1, l2) -> {});
}
【问题讨论】:
-
我建议您使用 JMH 来验证您的微基准。如果你得到奇怪的结果,很难说测试中是否存在真正的错误差异。
-
@PeterLawrey 我用 JMH 结果更新了 OP。我没有观察到两倍的加速,但我仍然观察到 JVM 的 64 位和 32 位版本之间的结果不一致。
primesOpt()在 JVM 64 位上的性能提高了 20%,但在 JVM 32 位上几乎差了 10%。这个结果仍然是我提出问题的原因:为什么我不能在 32 位和 64 位 JVM 上观察到相同的性能提升? -
我认为您应该从 profiling 两个平台上的基准测试开始。另外,打开 GC 日志记录。
-
你的内存设置是什么? GC 日志是什么样的?您的 PC 是否 100% 运行,磁盘交换等。在性能方面,您需要查看完整的环境设置以确保没有瓶颈。
-
collect(() -> res, ArrayList::add, (l1, l2) -> {})是对 API 的完全破坏性滥用。由于您实际上正在执行与forEachOrdered(res::add)等效的操作,因此请使用它。
标签: performance jvm java-8 java-stream