Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Java: what is the overhead of using ConcurrentSkipList* when no concurrency is needed?

I need a sorted list in a scenario dominated by iteration (compared to insert/remove, not random get at all). For this reason I thought about using a skip list compared to a tree (the iterator should be faster).

The problem is that java6 only has a concurrent implementation of a skip list, so I was guessing whether it makes sense to use it in a non-concurrent scenario or if the overhead makes it a wrong decision.

For what I know ConcurrentSkipList* are basically lock-free implementations based on CAS, so they should not carry (much) overhead, but I wanted to hear somebody else's opinion.

EDIT:

After some micro-benchmarking (running iteration multiple times on different-sized TreeSet, LinkedList, ConcurrentSkipList and ArrayList) shows that there's quite an overhead. ConcurrentSkipList does store the elements in a linked list inside, so the only reason why it would be slower on iteration than a LinkedList would be due to the aforementioned overhead.

like image 696
marcorossi Avatar asked Jan 18 '23 08:01

marcorossi


2 Answers

If thread-safety's not required I'd say to skip package java.util.concurrent altogether.

What's interesting is that sometimes ConcurrentSkipList is slower than TreeSet on the same input and I haven't sorted out yet why.

I mean, have you seen the source code for ConcurrentSkipListMap? :-) I always have to smile when I look at it. It's 3000 lines of some of the most insane, scary, and at the same time beautiful code I've ever seen in Java. (Kudos to Doug Lea and co. for getting all the concurrency utils integrated so nicely with the collections framework!) Having said that, on modern CPUs the code and algorithmic complexity won't even matter so much. What usually makes more difference is having the data to iterate co-located in memory, so that the CPU cache can do its job better.

So in the end I'll wrap ArrayList with a new addSorted() method that does a sorted insert into the ArrayList.

Sounds good. If you really need to squeeze every drop of performance out of iteration you could also try iterating a raw array directly. Repopulate it upon each change, e.g. by calling TreeSet.toArray() or generating it then sorting it in-place using Arrays.sort(T[], Comparator<? super T>). But the gain could be tiny (or even nothing if the JIT does its job well) so it might not be worth the inconvenience.

like image 181
Luke Usherwood Avatar answered Jan 20 '23 21:01

Luke Usherwood


As measured using Open JDK 6 on typical production hardware my company uses, you can expect all add and query operations on a skip-list map to take roughly double the time as the same operation on a tree map.

examples:

63 usec vs 31 usec to create and add 200 entries. 145 ns vs 77 ns for get() on that 200-element map.

And the ratio doesn't change all that much for smaller and larger sizes.

(The code for this benchmark will eventually be shared so you can review it and run it yourself; sorry we're not ready to do that yet.)

like image 29
Kevin Bourrillion Avatar answered Jan 20 '23 22:01

Kevin Bourrillion