为什么ConcurrentSkipListSet升序迭代器更快'而不是下降的?

时间:2018-06-07 10:08:51

标签: java collections iterator java.util.concurrent

我在ConcurrentSkipListSet上使用了descendingIterator方法。我刚检查了文档并注意到以下评论:

'升序有序视图及其迭代器比降序视图快。'

请参阅https://docs.oracle.com/javase/8/docs/api/java/util/concurrent/ConcurrentSkipListSet.html#descendingIterator--

不幸的是,它没有提供更多相关信息。有什么样的性能差异?它有意义吗?为什么会有性能差异?

2 个答案:

答案 0 :(得分:13)

如果您查看Skip Lists的维基百科页面,您会发现它们实际上是一种复杂的链表,其中的链接指向列表条目的排序方向。 (该图清楚地说明了这一点......)

当您向前移动跳过列表时,您只需按照链接进行操作即可。每个next()调用都是O(1)操作。

当您反向遍历跳过列表时,每个next()调用必须在返回最后一个键之前找到键。这是一个O(logN)操作。

(但是,向后遍历跳过列表仍然比向后遍历单个链接列表快得多。对于每个next()调用,这将是O(N)...)

如果你深入了解,你会发现ConcurrentSkipListSet实际上是ConcurrentSkipListMap的包装。在该类中,地图的跳过列表表示中的Node个对象在升序键方向上单独链接链......它遵循(从前一个)上升迭代比下降迭代更快。

性能差异将是显着的,并且随着设置大小的增加,由于O(1)与O(logN)的差异,它将变得更加显着。

答案 1 :(得分: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 repository中的代码示例,它适用于升序和降序迭代器:

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