如何使用并行流在Java中查找第n个质数

时间:2019-07-03 15:38:46

标签: java stream primes

似乎在使用有序流在难以限制的数字范围上处理短路操作时,parallel()无法使用。 例如:

public class InfiniteTest {

    private static boolean isPrime(int x) {
        if (x < 2) {
            return false;
        }
        if (x % 2 == 0 && x > 2) {
            return false;
        }
        // loop while i <= sqrt(x), using multiply for speedup
        for (int i = 3; i * i <= x; i += 2) {
            if (x % i == 0) {
                return false;
            }
        }
        return true;
    }

    private static int findNthPrime(final int n) {
        // must not use infinite stream, causes OOME
        // but even big size causes huge slowdown
        IntStream.range(1, 1000_000_000)            
                // .parallel()
                .filter(InfiniteTest::isPrime)
                .skip(n - 1)
                .findFirst()
                .getAsInt();
    }

    public static void main(String[] args) {
        int n = 1000; // find the nth prime number
        System.out.println(findNthPrime(n));
    }
}

此顺序流可以正常工作。但是,当我添加parallel()时,它似乎可以永远运行(或最终运行很长时间)。我认为这是因为流线程处理任意数字,而不是从流中的第一个数字开始。我不能bound the range of integers to scan for prime numbers有用。

那么,有没有任何简单的技巧可以在没有该陷阱的情况下与流并行运行此问题,例如,强制splititerator从流的开头开始处理大量工作?还是从覆盖越来越多的范围的子流中构建流? 还是以某种方式设置multithreading as producer/consumer pattern但带有流?

相似的问题似乎都试图阻止并行的使用:

2 个答案:

答案 0 :(得分:2)

除2和3外,所有质数均采用6n-1或6n + 1的形式。您已经在代码中将2视为特例。您可能还想尝试将3视为特殊:

if (x % 3 == 0) {
    return x == 3;
}

然后运行两个并行流,一个以5n开始的6n-1形式的测试编号,以7开头的6n + 1形式的其他测试编号。每个流一次可以跳过六个数字。

为了安全起见,您可以使用Prime Number theorem来估计第n个素数的值并将搜索范围设置为略高于该估计值。

答案 1 :(得分:0)

TL / DR :不可能。

似乎不可能以一种有用的方式(用“有用”的意思要好于按时间顺序查找时间)来用一种短路方法并行地处理无边界的流,以查找任何事物的最早出现(按流的顺序)。结果)。

说明 我尝试了AbstractIntSpliterator的自定义实现,该实现不将流拆分到分区(1-100、101-200等)中,而是交错地将它们拆分([0、2、4、6、8,...],[ 1,3,5,6 ...])。在顺序情况下这可以正常工作:

/**
 * Provides numbers starting at n, on split splits such that child iterator and
 * this take provide interleaving numbers
 */
public class InterleaveSplitIntSplitIterator extends Spliterators.AbstractIntSpliterator {

    private int current;
    private int increment;

    protected InterleaveSplitIntSplitIterator(int start, int increment) {
        super(Integer.MAX_VALUE,
                        Spliterator.DISTINCT
                        // splitting is interleaved, not prefixing
                        // | Spliterator.ORDERED
                        | Spliterator.NONNULL
                        | Spliterator.IMMUTABLE
                        // SORTED must imply ORDERED
                        // | Spliterator.SORTED
        );
        if (increment == 0) {
            throw new IllegalArgumentException("Increment must be non-zero");
        }
        this.current = start;
        this.increment = increment;
    }

    @Override
    public boolean tryAdvance(IntConsumer action) {
        // Don't benchmark with this on
        // System.out.println(Thread.currentThread() + " " + current);
        action.accept(current);
        current += increment;
        return true;
    }

    // this is required for ORDERED even if sorted() is never called
    @Override
    public Comparator<? super Integer> getComparator() {
        if (increment > 0) {
            return null;
        }
        return Comparator.<Integer>naturalOrder().reversed();
    }

    @Override
    public OfInt trySplit() {
        if (increment >= 2) {
            return null;
        }
        int newIncrement = this.increment * 2;
        int oldIncrement = this.increment;

        this.increment = newIncrement;
        return new InterleaveSplitIntSplitIterator(current + oldIncrement, newIncrement);
    }

    // for convenience
    public static IntStream asIntStream(int start, int increment) {
        return StreamSupport.intStream(
                new InterleaveSplitIntSplitIterator(start, increment),
                /* no, never set parallel here */ false);
    }
}

但是,此类流不能具有Spliterator.ORDERED特性,因为

  

如果是这样,此Spliterator保证该方法        {@link #trySplit}分割元素的严格前缀

并且这也意味着这样的流无法保留其SORTED特性,因为

  

报告{@code SORTED}的拆分器也必须报告{@code ORDERED}

因此,我的splititerator并行出现了(有些)混乱的数字,必须在应用限制之前通过排序将其固定,这不适用于无限流(通常情况下)。

因此,为此的所有解决方案都必须使用splititerator,将其拆分为块或前缀数据,然后以〜任意顺序使用它们,这导致超出实际结果处理范围的许多数字范围,通常比(慢)得多顺序解决方案。

因此,除了限制要测试的数字范围外,似乎没有使用并行流的解决方案。问题在于规范中要求ORDERED特性通过前缀来拆分Stream,而不是提供不同的方法来重组来自多个splititerator的有序流结果。

但是,仍然可以使用具有并行处理(缓冲)输入的顺序流的解决方案(但不像调用parallel()那样简单)。