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()
)来使用Java8
IntStream
收集前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)
等效的操作,所以只需使用它即可。