我正在测试两种不同的方法(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
。之后,您可以使用以下命令运行基准:{{1} })。
第一个示例(java -jar target\benchmarks.jar
方法)是一种将N个素数收集到primes()
的简单算法。第二个(List<Integer>
方法)是一种增强方法,它只测试前一个素数的除法。
我用JMH测试两个实现来计算素数的primesOpt()
,最大值为10,000:
List<Integer>
根据JVM架构的不同,我获得了不同的加速比。在JVM 64位中,我发现@Benchmark
public int testPrimes() {
return primes(10_000).size();
}
@Benchmark
public int testPrimesOpt() {
return primesOpt(10_000).size();
}
的速度比标准版本primesOpt()
高25%,而对于JVM 32位则没有加速。
JRE 1.8.0_91-b14 64-Bit的结果:
primes()
JRE 1.8.0_91-b14 32-Bit的结果:
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
这些测试是在具有双核Intel I7 Cpu的机器上进行的,具有超线程,产生2个内核和4个硬件线程。此外,该系统有4GB的RAM。使用的JVM版本是1.8.0_91-b14,在Windows 7上运行。基准测试执行时的最小堆大小为1024MB(对应于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
)。在测量期间,没有其他活动在运行。
您是否知道为什么我不能在优化版本的质数算法上观察JVM 32位的相同性能改进?
-Xms1024M
方法实现:
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()
答案 0 :(得分:3)
我无法重现您的结果,但通常情况下,性能会因环境因素而有很大差异。在您的代码中,takewhile
方法强制处理盒装 Integer
值,而非优化变体isPrime
仅处理int
值。< / p>
这种权衡应该能够支付您请求的更多素数,即如果扫描第一个10_000
数字显示出矛盾的结果,请尝试100_000
或1_000_000
。拳击开销在最坏的情况下是线性的,一个不错的JVM可能会将其转换为亚线性甚至是恒定的开销,而将分割限制为实际质数的改进应该高于线性,因为素数的密度随着数字的增加而下降。
所以你使用的64Bit JVM在处理盒装值时可能会有更高的开销,但我认为,使用更高max
的测试也会为优化的变体带来优势 - 除非JVM知道一个技巧显着降低分工的成本。
但不应忽视您的优化变体在几个方面被打破。您正在将供应商() -> res
传递给违反合同的collect
,因为它不会在每次评估时返回新容器,并且收集器与前面{{1}中使用的谓词之间存在干扰}步骤。
这表明尝试优化基于Stream的解决方案可能不会导致某个地方。与以下直接方法比较:
filter
它避免了所有分区和装箱操作处于不利用Stream操作进行值选择的“劣势”,但仅用于创建最终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());
}
。在我的机器上,它比List<Integer>
元素的优化变体快10倍,10_000
元素快50倍。这表明,性能差异大约为10%,20%甚至是因果二或三,不值得讨论。
我不知道如何使用Stream API表达此算法。最重要的是,并非所有操作都受益于Stream API。