Почему ConcurrentSkipListSet возрастает, итераторы "быстрее", чем нисходящие? - программирование
Подтвердить что ты не робот

Почему ConcurrentSkipListSet возрастает, итераторы "быстрее", чем нисходящие?

Im использует метод descendingIterator для ConcurrentSkipListSet. Я только что проверил документацию и заметил следующий комментарий:

"Восходящие упорядоченные виды и их итераторы быстрее, чем нисходящие.

См. Https://docs.oracle.com/javase/8/docs/api/java/util/concurrent/ConcurrentSkipListSet.html#descendingIterator--

К сожалению, он не предоставляет больше информации об этом. Какая разница в производительности? это важно? и почему существует разница в производительности?

4b9b3361

Ответ 1

Если вы посмотрите на страницу Википедии " Пропустить списки", вы увидите, что они представляют собой сложную форму связанного списка со ссылками, идущими в направлении упорядочения записей списка. (На диаграмме это ясно видно...)

Когда вы перемещаете список пропусков в прямом направлении, вы просто следуете ссылкам. Каждый next() вызов является операцией O (1).

Когда вы перемещаете список пропусков в обратном направлении, каждый next() вызов должен найти ключ перед возвратом последнего ключа. Это операция O (logN).

(Тем не менее, перемещение списка пропусков в обратном направлении по-прежнему значительно быстрее, чем перемещение одиночно связанного списка назад. Это будет O (N) для каждого next() вызова next()...)

Если вы посмотрите под капот, вы увидите, что ConcurrentSkipListSet на самом деле является оберткой для ConcurrentSkipListMap. В этом классе объекты Node в представлении списка пропусков карты образуют односвязные цепи... в направлении восходящего ключа. Из предыдущего следует, что возрастающая итерация быстрее, чем нисходящая итерация.

Разница в производительности будет значительной, и она станет более значительной по мере увеличения установленного размера из-за разности O (1) по сравнению с O (logN).

Ответ 2

В дополнение к ответу Стивена я написал простой контрольный показатель:

@BenchmarkMode(Mode.AverageTime)
@OutputTimeUnit(TimeUnit.MILLISECONDS)
@Warmup(iterations = 5, time = 5, timeUnit = TimeUnit.SECONDS)
@Measurement(iterations = 5, time = 5, timeUnit = TimeUnit.SECONDS)
@State(Scope.Thread)
public class ConcurrentSkipListSetIteratorTest {

    @Fork(1)
    @Benchmark
    public void ascItr(SetupParams params) {
        Iterator<Integer> itr = params.cslset.iterator();
        while (itr.hasNext()) itr.next();
    }

    @Fork(1)
    @Benchmark
    public void dscItr(SetupParams params) {
        Iterator<Integer> itr = params.cslset.descendingIterator();
        while (itr.hasNext()) itr.next();
    }

    @State(Scope.Benchmark)
    public static class SetupParams {

        private ConcurrentSkipListSet<Integer> cslset;

        @Setup(Level.Invocation)
        public void setUp() {
            cslset = new SplittableRandom()
                .ints(100_000, 0, 100_000)
                .boxed()
                .collect(Collectors.toCollection(ConcurrentSkipListSet::new));
        }
    }
}

Основной метод:

public static void main(String[] args) throws RunnerException {
    Options opt = new OptionsBuilder()
        .include(ConcurrentSkipListSetIteratorTest.class.getSimpleName())
        .jvmArgs("-ea", "-Xms512m", "-Xmx1024m")
        .shouldFailOnError(true)
        .build();
    new Runner(opt).run();
}

Кроме того, здесь приведен пример кода из репозитория JDK 10 который используется в восходящем и нисходящем итераторах соответственно:

private void ascend() {
    ...
    for (;;) {
        // there is a link to the next node
        next = next.next; // O(1) operation
        ...
    }
}

private void descend() {
    ...
    for (;;) {
        // but, there is no link to the previous node
        next = m.findNear(lastReturned.key, LT, cmp); // O(logN) operation
        ...
    }
}

Окончательные результаты для 10_000 элементов:

Benchmark  Mode  Cnt  Score   Error  Units
ascItr     avgt    5  0,075 ± 0,029  ms/op
dscItr     avgt    5  0,810 ± 0,116  ms/op

И для 100_000 элементов:

Benchmark  Mode  Cnt   Score   Error  Units
ascItr     avgt    5   2,764 ± 1,160  ms/op
dscItr     avgt    5  11,110 ± 0,937  ms/op

Визуализация разницы в производительности:

enter image description here