Подтвердить что ты не робот

Когда следует разбить Spliterator?

Я понимаю, что есть накладные расходы при настройке обработки параллельного Stream, и что обработка в одном потоке выполняется быстрее, если есть несколько элементов или обработка каждого элемента выполняется быстро.

Но существует ли аналогичный порог для trySplit(), точка, где разложение проблемы на более мелкие куски контрпродуктивно? Я думаю по аналогии с переключением сортировки слияния на сортировку вставки для самых маленьких кусков.

Если да, то пороговое значение зависит от относительной стоимости trySplit() и потребления предмета в течение tryAdvance()? Рассмотрим операцию разделения, которая намного сложнее, чем продвижение индекса массива — например, разделение лексически упорядоченной мультимножествой перестановки. Существует ли соглашение, позволяющее клиентам определить нижний предел для разделения при создании параллельного потока, в зависимости от сложности их потребителя? Эвристика Spliterator может использовать для оценки самого нижнего предела?

Или, в качестве альтернативы, всегда безопасно ли нижний предел a Spliterator равным 1, и пусть алгоритм обработки во времени позаботится о том, следует ли продолжать разделение или нет?

4b9b3361

Ответ 1

В общем, вы не представляете, сколько работы сделано для пользователя, прошедшего через tryAdvance или forEachRemaining. Ни поток, ни FJP не знают об этом, так как это зависит от кода, предоставленного пользователем. Это может быть намного быстрее или намного медленнее, чем процедура расщепления. Например, вы можете вводить два элемента, но обработка каждого элемента занимает один час, поэтому разделение этого ввода очень разумно.

Я обычно разбиваю вход как можно больше. Существует три трюка, которые можно использовать для улучшения расщепления:

  • Если трудно разделить поровну, но вы можете отслеживать (или, по крайней мере, грубо оценить) размер каждой части, не стесняйтесь делиться неравномерно. Реализация потока будет делать дальнейшее разделение для большей части. Не забывайте о характеристиках SIZED и SUBSIZED.

  • Переместите твердую часть разделения на следующий вызов tryAdvance/forEachRemaining. Например, предположим, что у вас есть известное количество перестановок, а в trySplit вы переходите к другой перестановке. Что-то вроде этого:

    public class MySpliterator implements Spliterator<String> {
        private long position;
        private String currentPermutation;
        private final long limit;
    
        MySpliterator(long position, long limit, String currentPermutation) {
            this.position = position;
            this.limit = limit;
            this.currentPermutation = currentPermutation;
        }
    
        @Override
        public Spliterator<String> trySplit() {
            if(limit - position <= 1)
                return null;
            long newPosition = (position+limit)>>>1;
            Spliterator<String> prefix = 
                     new MySpliterator(position, newPosition, currentPermutation);
            this.position = newPosition;
            this.currentPermutation = calculatePermutation(newPosition); // hard part
            return prefix;
        }
    
        ...
    }
    

    Переместите твердую часть на следующий вызов tryAdvance следующим образом:

    @Override
    public Spliterator<String> trySplit() {
        if(limit - position <= 1)
            return null;
        long newPosition = (position+limit)>>>1;
        Spliterator<String> prefix = 
                 new MySpliterator(position, newPosition, currentPermutation);
        this.position = newPosition;
        this.currentPermutation = null;
        return prefix;
    }
    
    @Override
    public boolean tryAdvance(Consumer<? super String> action) {
        if(currentPermutation == null)
            currentPermutation = calculatePermutation(position); // hard part
        ...
    }
    

    Таким образом, жесткая часть также будет выполняться параллельно с обработкой префикса.

  • Если у вас осталось не так много элементов в текущем разделителе (например, менее 10), и запрос на рассылку был запрошен, вероятно, это хорошо, только чтобы перейти к половине ваших элементов, собирая их в массив, а затем создать разделитель на основе массива для этого префикса (аналогично тому, как это делается в AbstractSpliterator.trySplit()). Здесь вы управляете всем кодом, так что вы можете заранее измерить, насколько нормальная trySplit медленнее, чем tryAdvance, и оценить порог, когда вы должны переключиться на разделение на основе массива.