Как найти n-е простое число в Java, используя параллельные потоки - PullRequest
1 голос
/ 03 июля 2019

Кажется, что при использовании упорядоченных потоков для обработки операции короткого замыкания в трудно ограниченном числовом диапазоне, 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(), кажется, что он работает вечно (или, наконец, очень долго).Я предполагаю, что это потому, что потоки потока работают с произвольными числами вместо того, чтобы начинаться с первых чисел в потоке.Я не могу с пользой ограничить диапазон целых чисел для поиска простых чисел .

Так есть ли какой-нибудь простой способ запустить эту проблему параллельно с потоками без этой ловушки, например, заставить сплиттераторслужить куски работы с самого начала потока?Или построение потока из подпотоков, которые покрывают растущие диапазоны номеров?Или как-то настроить многопоточность в качестве шаблона производителя / потребителя , но с потоками?

Похоже, что все подобные вопросы пытаются препятствовать использованию параллели:

Ответы [ 2 ]

2 голосов
/ 05 июля 2019

Кроме 2 и 3, все простые числа имеют вид 6n-1 или 6n + 1. Вы уже рассматриваете 2 как особый случай в своем коде. Вы можете попробовать обработать 3 как особенное:

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

И затем запустить два параллельных потока: один тестовый номер в форме 6n-1, начиная с 5, а другой тестовый номер в форме 6n + 1, начиная с 7. Каждый поток может пропустить шесть чисел одновременно.

Вы можете использовать теорему простого числа , чтобы оценить значение n-го простого числа и установить предел вашего поиска немного выше, чем оценка безопасности.

0 голосов
/ 06 июля 2019

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}разделяет строгий префикс элементов

, и это также означает, что такой поток не может сохранять свои отсортированные характеристики, потому что

Spliterator, который сообщает {@code SORTED}, также должен сообщать {@code ORDERED}

Таким образом, мой параллельный сплиттератор имеет (несколько) перемешанные числа, которые должны быть исправлены путем сортировки перед применением предела, что плохо работает с бесконечными потоками (в общем случае).

Таким образом, все решения для этого должны использовать сплиттератор, который разбивает на фрагменты или префиксные данные, которые затем используются в произвольном порядке, что приводит к обработке многих числовых диапазонов за пределами фактического результата, становясь) в целом медленнее, чем последовательное решение.

Таким образом, кроме ограничения диапазона номеровТест, кажется, не может быть решения с использованием параллельного потока.Проблема заключается в спецификации, требующей, чтобы характеристики ORDERED разделяли поток с помощью префикса, вместо того, чтобы предоставлять различные средства повторной сборки упорядоченного потока, полученного из нескольких разделителей.

Однако решение, использующее последовательный поток с параллельной обработкой (буферизованной)входные данные все еще возможны (но не так просто, как вызов parallel()).

...