Performance 为什么可以’;我在JVM 32位和64位上观察到相同的性能改进吗?
我正在测试两种不同的方法(Performance 为什么可以’;我在JVM 32位和64位上观察到相同的性能改进吗?,performance,jvm,java-8,java-stream,Performance,Jvm,Java 8,Java Stream,我正在测试两种不同的方法(primes()和primesOpt())来使用Java8IntStream收集前N个素数。我从第六章中选取了这些例子。您可以从本文的要点中获得源代码,并使用Maven和JMH集成来构建它。(您可以将pom.xml复制到项目文件夹中,并将Primes.java复制到src\main\java\Primes中,然后使用命令mvn clean install构建它。之后,您可以使用以下命令运行基准:java-jar target\benchmarks.jar) 第一个示例(
primes()
和primesOpt()
)来使用Java8IntStream
收集前N个素数。我从第六章中选取了这些例子。您可以从本文的要点中获得源代码,并使用Maven和JMH集成来构建它。(您可以将pom.xml
复制到项目文件夹中,并将Primes.java
复制到src\main\java\Primes
中,然后使用命令mvn clean install
构建它。之后,您可以使用以下命令运行基准:java-jar target\benchmarks.jar
)
第一个示例(primes()
方法)是一个简单的算法,用于将N个素数收集到列表中。第二种方法(primesOpt()
method)是一种增强的方法,它只测试前面素数的除法
我使用JMH测试这两种实现,以计算质数的列表
,最大值为10000:
@Benchmark
public int testPrimes() {
return primes(10_000).size();
}
@Benchmark
public int testPrimesOpt() {
return primesOpt(10_000).size();
}
根据JVM架构的不同,我得到了不同的加速比。在JVM 64位中,我观察到primesop()
的加速比标准版本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版本是运行在Windows7上的1.8.0_91-b14。基准测试以1024MB的最小堆大小执行(对应于-Xms1024M
)。在测量期间,没有其他活动在运行
你知道为什么我不能在优化版的primes算法的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);
}
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) -> {});
}
我无法复制您的结果,但一般来说,性能可能会因环境因素而显著不同。在代码中,takewhile
方法强制处理装箱的Integer
值,而非优化变量isPrime
仅处理int
值
这种权衡应该会让你要求的素数越多,也就是说,如果扫描第一个10\u 000
数字显示矛盾的结果,请尝试100\u 000
或1\u 000
。装箱开销在最坏的情况下是线性的,一个好的JVM可能会把它变成一个次线性的,甚至是恒定的开销,而限制实际素数的划分的改进应该比线性的更高,因为素数密度随着数字的增加而下降
因此,在处理装箱值时,您使用的64位JVM可能会有更高的开销,但我假设,使用更高的max
进行测试也会显示优化变量的优势,除非JVM知道显著降低除法操作成本的诀窍
但不应该忽视的是,您的优化变体在几个方面都被破坏了。您正在将供应商()->res
传递给收集
,这违反了合同,因为它在每次评估时都不返回新容器,并且收集程序和前面筛选
步骤中使用的谓词之间存在干扰
这表明,尝试优化基于流的解决方案可能不会带来什么结果。与以下直接方法相比:
public static List<Integer> primesBest(int max) {
BitSet prime=new BitSet();
prime.set(1, max>>1);
for(int i=3; i<max; i+=2)
if(prime.get((i-1)>>1))
for(int b=i*3; b<max; b+=i*2) prime.clear((b-1)>>1);
return IntStream.concat( IntStream.of(2),
prime.stream().map(i->i+i+1)).boxed().collect(Collectors.toList());
}
公共静态列表primesBest(int max){
位集素数=新位集();
prime.set(1,max>>1);
对于(int i=3;i>1))
对于(intb=i*3;b>1);
返回IntStream.concat(IntStream.of(2),
prime.stream().map(i->i+i+1)).boxed().collect(Collectors.toList());
}
它避免了所有的除法和装箱操作,其“缺点”是不使用流操作进行值选择,而仅用于创建最终的列表。在我的机器上,对于10_000
元素,它比您的优化变体快10倍左右,对于1_000
元素,它快50倍左右。这表明,10%、20%甚至二、三倍的性能差异不值得讨论
不过,我不知道如何使用流API来表达这个算法。底线可能是并非所有操作都能从StreamAPI中获益。我建议您使用JMH来验证您的微基准测试。如果您得到奇怪的结果,很难说测试中是否存在真正的错误差异。@PeterLawrey我用JMH结果更新了OP。我没有观察到两倍的加速,但我仍然观察到JVM的64位和32位版本之间的结果不一致。primesOpt()
在JVM 64位上的性能提高了20%,但在JVM 32位上的性能差了近10%。这个结果仍然是我的问题的原因:为什么我不能在32位和64位JVM上观察到相同的性能改进?我认为您应该从分析两种平台上的基准测试的两个版本开始。另外,打开GC日志记录。您的内存设置是什么?GC日志是什么样子的?您的电脑是否运行100%,磁盘交换等。从性能角度看,您需要查看完整的环境设置以确保没有瓶颈。收集(()->res,ArrayList::add,(l1,l2)->{}是对API的完全滥用。因为您实际上是在做与forEachOrdered(res::add)
等效的操作,所以只需使用它即可。