【问题标题】:Making a Parallel IntStream more efficient/faster?使并行 IntStream 更高效/更快?
【发布时间】:2018-02-12 19:29:43
【问题描述】:

我已经为这个答案寻找了一段时间,但找不到任何东西。

我正在尝试创建一个可以非常快速地找到素数的 IntStream(很多很多素数,非常快——几秒钟内数百万)。

我目前正在使用这个并行流:

import java.util.stream.*;
import java.math.BigInteger;

public class Primes {
    public static IntStream stream() {
        return IntStream.iterate( 3, i -> i + 2 ).parallel()
                .filter( i -> i % 3 != 0 ).mapToObj( BigInteger::valueOf )
                .filter( i -> i.isProbablePrime( 1 ) == true )
                .flatMapToInt( i -> IntStream.of( i.intValue() ) );
    }
}

但是生成数字需要很长时间。 (生成 1,000,000 个素数需要 7546 毫秒)。

有什么明显的方法可以提高效率/速度吗?

【问题讨论】:

  • 在尝试提出更多问题之前,请阅读How do I ask a good question?
  • 买一台更快的电脑:p
  • 最明显的方法是不使用流。
  • @shmosel 对此一无所知,他似乎确实有足够的数据来实际利用 parallel ,如果没有流,这将不是那么容易
  • 只是一种风格说明:从不使用… == true.flatMapToInt( i -> IntStream.of( i.intValue() ) ) 的目的到底是什么?为什么不直接使用.mapToInt(i -> i.intValue()).mapToInt(BigInteger::intValue)

标签: java parallel-processing java-stream primes


【解决方案1】:

对于代码的高效并行处理存在两个普遍问题。首先,使用iterate,这不可避免地需要前一个元素计算下一个元素,这不是并行处理的良好起点。其次,您使用的是无限流。高效的工作负载拆分至少需要估计要处理的元素数量。

由于您正在处理升序整数,因此在达到Integer.MAX_VALUE 时有一个明显的限制,但流实现并不知道您实际上正在处理升序数字,因此,会将您的正式无限流视为真正的无限。

解决这些问题的解决方案是

public static IntStream stream() {
    return IntStream.rangeClosed(1, Integer.MAX_VALUE/2).parallel()
            .map(i -> i*2+1)
            .filter(i -> i % 3 != 0).mapToObj(BigInteger::valueOf)
            .filter(i -> i.isProbablePrime(1))
            .mapToInt(BigInteger::intValue);
}

但必须强调的是,在这种形式下,该解决方案仅在您确实想要处理整个整数范围内的所有或大部分素数时才有用。一旦您将skiplimit 应用于流,并行性能将显着下降,正如这些方法的文档所指定的那样。此外,将 filter 与仅接受较小数值范围内的值的谓词一起使用意味着会有很多不必要的工作,最好不要并行完成。

您可以调整该方法以接收值范围作为参数,以调整源IntStream 的范围来解决此问题。

现在是强调算法相对于并行处理的重要性的时候了。考虑Sieve of Eratosthenes。以下实现

public static IntStream primes(int max) {
    BitSet prime = new BitSet(max>>1);
    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>0 && b<max; b += i*2) prime.clear((b-1)>>1);
    return IntStream.concat(IntStream.of(2), prime.stream().map(i -> i+i+1));
}

尽管没有使用并行处理,但与其他方法相比,速度要快一个数量级,即使使用Integer.MAX_VALUE 作为上限(使用.reduce((a,b) -&gt; b) 的终端操作而不是toArray 或@ 987654333@,以确保在不增加额外存储或打印成本的情况下完整处理所有值。

要点是,isProbablePrime 非常适合当您有特定的候选人或想要处理小范围的数字时(或者当数字超出 int 甚至 long 范围时)¹,但对于处理大量的素数升序有更好的方法,并行处理并不是性能问题的最终答案。


¹考虑,例如

Stream.iterate(new BigInteger("1000000000000"), BigInteger::nextProbablePrime)
      .filter(b -> b.isProbablePrime(1))

【讨论】:

    【解决方案2】:

    通过进行一些修改,我似乎可以比你现有的做得好 1/2:

    return IntStream.iterate(3, i -> i + 2)
                .parallel()
                .unordered()
                .filter(i -> i % 3 != 0)
                .mapToObj(BigInteger::valueOf)
                .filter(i -> i.isProbablePrime(1))
                .mapToInt(BigInteger::intValue);
    

    【讨论】:

    • filter(i -&gt; i % 3 != 0)的原因是什么?是否只是过滤那些是 3 的倍数的数字?如果是,它将带来多少性能改进?
    猜你喜欢
    • 2017-01-29
    • 1970-01-01
    • 2018-03-08
    • 2015-03-16
    • 2013-08-11
    • 2019-11-22
    • 2013-08-05
    • 2020-06-18
    • 1970-01-01
    相关资源
    最近更新 更多