问题
I’m using the descendingIterator method on ConcurrentSkipListSet. I’ve just checked the documentation and noticed the following comment:
‘Ascending ordered views and their iterators are faster than descending ones.’
See https://docs.oracle.com/javase/8/docs/api/java/util/concurrent/ConcurrentSkipListSet.html#descendingIterator--
Unfortunately it doesn’t provide any more information on this. What kind of performance difference is there? is it significant? and why is there a performance difference?
回答1:
If you look at the Wikipedia page for Skip Lists you will see that they are effectively a complicated form of linked list with the links going in the direction of an ordering of the list entries. (The diagram illustrates this clearly ...)
When you traverse the skip list in the forward direction, you are simply following the links. Each next()
call is an O(1) operation.
When you traverse the skip list in the reverse direction, each next()
call has to find the key before the last key returned. This is an O(logN) operation.
(However, traversing a skip list backwards is still substantially faster than traversing a singly linked list backwards. That would be O(N) for each next()
call ...)
If you look under the hood, you will see that a ConcurrentSkipListSet
is actually a wrapper for a ConcurrentSkipListMap
. In that class, the Node
objects in the skip list representation of the map form singly linked chains ... in the ascending key direction. It follows (from the previous) that ascending iteration is faster than descending iteration.
The performance difference will be significant, and it will get more significant as the set size increases because of the O(1) versus O(logN) difference.
回答2:
In addition to Stephen's answer, I wrote a simple Benchmark:
@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));
}
}
}
Main method:
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();
}
Also, here is a code example from the JDK 10
repository that is used in ascending and descending iterators appropriately:
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
...
}
}
Final results for 10_000
elements:
Benchmark Mode Cnt Score Error Units
ascItr avgt 5 0,075 ± 0,029 ms/op
dscItr avgt 5 0,810 ± 0,116 ms/op
And for 100_000
elements:
Benchmark Mode Cnt Score Error Units
ascItr avgt 5 2,764 ± 1,160 ms/op
dscItr avgt 5 11,110 ± 0,937 ms/op
Visualizing performance difference:
来源:https://stackoverflow.com/questions/50738555/why-are-concurrentskiplistset-ascending-iterators-faster-than-descending-ones