0

I want to compare performance between ConcurrentHashMap and ConcurrentSkipListMap. It's for studying purpose. Of corse the result depends on platform to platform. On my computer expectedly the reading test ConcurrentHashMap more productive then ConcurrentSkipListMap. But the writing test showed more performance ConcurrentSkipListMap. ConcurrentHashMap relies on a hash table, I think it should be more faster. Why is it happen?

package Concurrency;

import java.util.*;
import java.util.concurrent.*;

abstract class Task implements Callable<Long> {
    protected static Map<Integer, String> map;
    protected int nIteration; 
    protected static int index;
    protected long startTime, endTime;
    private static Random random = new Random();
    private static char[] chars = "abcdefghijklmnopqrstuvwxyz".toCharArray();
    public Task(Map<Integer, String> map, int nIteration) {
        Task.map = map;
        this.nIteration = nIteration;
    }
    protected static synchronized String getNextString() {
        StringBuilder sb = new StringBuilder();
        for (int i = 0; i < 5; i++) {
            char c = chars[random.nextInt(chars.length)];
            sb.append(c);
        }
        sb.append(index);
        return sb.toString();
    }
    protected static synchronized int getNextInt() {  return index++; }
    protected static synchronized int getPreviousInt() { return index--; } 
    protected static synchronized int getCurrentInt() { return index; } // It's for test purpose.
    public abstract Long call();
}

class WriterTask extends Task {
    public WriterTask(Map<Integer, String> map, int nIteration) { super(map, nIteration); }
    public Long call() {
        startTime = System.currentTimeMillis();
        while(nIteration-- > 0) {
            map.put(getNextInt(), getNextString());
        }
        endTime = System.currentTimeMillis();
        return (endTime - startTime);

    }
}

class ReaderTask extends Task {
    public ReaderTask(Map<Integer,String> map, int nIteration) { super(map, nIteration); }
    @Override
    public Long call() {
        startTime = System.currentTimeMillis();
        while(nIteration-- > 0) {
            map.remove(getPreviousInt());
        }
        endTime = System.currentTimeMillis();
        return (endTime - startTime);
    }
}

public class FourtyThree {
    private static List<Future<Long>> result = new LinkedList<>();
    private static Map<Integer, String> map;
    //private static String mapName;
    private static Map<String, Double> makeReport(
            int nCycle, int nThreads, boolean isWriter , int nIteration) 
                    throws InterruptedException, ExecutionException {
        Long resultTime = 0L;
        int numberLine = 0;
        double resultAverage;
        StringBuilder sb = new StringBuilder();
        sb.append(map.getClass().getSimpleName());
        sb.append(", Cycle:" + nCycle);
        if(isWriter)
            sb.append(", Test type:Writing");
        else
            sb.append(", Test type: Reading");
        sb.append(", iteration:" + nIteration);
        sb.append(", Threads:" +nThreads);
        for(Future<Long> i : result) {
            resultTime += i.get();
            numberLine++;
        }
        resultAverage = (double)resultTime / (double)numberLine;
        resultAverage = (double)Math.round(resultAverage * 100) / 100;
        sb.append(", Average time:" + resultAverage + " milliseconds");
        return Collections.singletonMap(sb.toString(), resultAverage);

    }
    private static void prepareReading(int nIteration) {
        ExecutorService exec = Executors.newSingleThreadExecutor();
        exec.submit(new WriterTask(map, nIteration));
        exec.shutdown();
    }
    public static Map<String, Double> test( Map<Integer, String> testMap, 
                                            int nCycle,
                                            int nThreads, 
                                            boolean isWriter , 
                                            int nIteration ) 
                                               throws InterruptedException, ExecutionException {

        map = testMap;
        if (!isWriter) 
            prepareReading(nThreads * nIteration);
        ExecutorService exec = Executors.newFixedThreadPool(nThreads);
        List<Callable<Long>> tasks = new LinkedList<>();
        for(int i = 0; i < nThreads; i++) {
            if(isWriter)
                tasks.add(new WriterTask(map, nIteration));
            else
                tasks.add(new ReaderTask(map, nIteration));
        }
        result = exec.invokeAll(tasks);
        exec.shutdown();
        map.clear();
        return makeReport(nCycle, nThreads, isWriter , nIteration);
    }


    public static void main(String[] args) throws InterruptedException, ExecutionException {
        Map<String, Double> results = new LinkedHashMap<String, Double>();
        Collection<Double> resultTime = results.values();
        double time = 0;
        ConcurrentHashMap<Integer, String> map1 = new ConcurrentHashMap<>();
        ConcurrentSkipListMap<Integer, String> map2 = new ConcurrentSkipListMap<>();
        for(int i = 0; i < 5; i++) {
            results.putAll(test(map1, i, 16, false, 1000));
        }
        for(Map.Entry<String, Double> entry : results.entrySet()) {
            System.out.println(entry.getKey());
            time += entry.getValue();
        }
        time = time / (double)resultTime.size();
        time = Math.round(time * 100) / 100;
        System.out.print("Average time for all cycles:" + time);
        System.out.print(", Max time:" + Collections.max(resultTime));
        System.out.print(", Min time:" + Collections.min(resultTime));

    }
}
/* Short report:

*** Reading ***
ConcurrentHashMap, Cycle:4, Test type: Reading, iteration:1 000 000, Threads:2
Average time for all cycles:3530.0, Max time:6817.5, Min time:1625.0

ConcurrentSkipListMap, Cycle:4, Test type: Reading, iteration:1 000 000, Threads:2
Average time for all cycles:4716.0, Max time:9337.5, Min time:1294.0

ConcurrentHashMap, Cycle:4, Test type: Reading, iteration:100 000, Threads:16
Average time for all cycles:528.0, Max time:1064.06, Min time:355.25

ConcurrentSkipListMap, Cycle:4, Test type: Reading, iteration:100 000, Threads:16
Average time for all cycles:1081.0, Max time:1732.75, Min time:330.5

*** Writing ***
ConcurrentHashMap, Cycle:4, Test type:Writing, iteration:1 000 000, Threads:2
Average time for all cycles:12112.1, Max time:18261.5, Min time:9111.5

ConcurrentSkipListMap, Cycle:4, Test type:Writing, iteration:1 000 000, Threads:2
Average time for all cycles:11856.7, Max time:18143.0, Min time:8292.0

ConcurrentHashMap, Cycle:4, Test type:Writing, iteration:100 000, Threads:16
Average time for all cycles:9015.0, Max time:16461.75, Min time:5016.5

ConcurrentSkipListMap, Cycle:4, Test type:Writing, iteration:100 000, Threads:16
Average time for all cycles:8922.68, Max time:12383.31, Min time:6783.13


*/
Alex
  • 751
  • 1
  • 9
  • 28
  • 2
    Before analyzing results - are your results [statistical significant](http://en.wikipedia.org/wiki/Statistical_significance)? – amit Mar 02 '15 at 10:26
  • 1
    http://stackoverflow.com/questions/504103/how-do-i-write-a-correct-micro-benchmark-in-java => you should probably use jmh instead of manually coding your benchmark (you don't seem to allow warmup, you run all the tests in the same method etc, – assylias Mar 02 '15 at 10:26
  • @assylias - I used 5 cycles, the first the most slow, the last - the most fast. After it I took average time. I think it is warmup. – Alex Mar 02 '15 at 10:31
  • @Alex you should discard the first cycles as they are not representative and somewhat random. – assylias Mar 02 '15 at 10:32
  • @amit - My results are not statistical significant, but I think `ConcurrentHashMap` has to be always more faster. – Alex Mar 02 '15 at 10:33
  • @Alex Nope. there are tons of things that you didn't factor in (other tasks running on the machine, worst case performance of hash map that is worse than skip list, gc started working in bad time, ....) Without statistical significance, you have no clue if your assumption is correct or not (with statistical significance you cannot be sure as well, but you can get as close as you want to it, which is much better) – amit Mar 02 '15 at 10:36
  • @assylias - I thought about it when I wrote the test. Maybe you are right. The first result doesn't represent the fair result. – Alex Mar 02 '15 at 10:40
  • @assylias - http://stackoverflow.com/questions/504103/how-do-i-write-a-correct-micro-benchmark-in-java - It is very good topic, thank you. – Alex Mar 02 '15 at 10:53
  • 1
    @Alex - if you are agreed that this just poor benchmarking, the best thing to do would be to delete your Question. There is nothing to be gained from any analysis of the dubious performance figures. – Stephen C Mar 02 '15 at 11:07
  • There are significant differences in the hash map implementations of Java 7 and Java 8 so that reporting the tested JRE’s version number is important. And your benchmark code involves so many `synchronized` methods that you can’t actually call it a *concurrency* test. – Holger Mar 02 '15 at 16:25
  • you should remove unnecessary synchronizations in Task and then you may try to increase the iteration count to see what happens. – Onur Aktaş Mar 03 '15 at 00:35
  • @Holger - I use Java 7. I can remove `synchronized` methods, and use `AtomicInteger` instead it. But what can I do with string? If I removed `synchronized` it would be wrong result because another thread can access the same data the same time. – Alex Mar 03 '15 at 06:18
  • An `AtomicInteger` is an improvement but still limiting the concurrency. The solution is simple, just let each thread use its own counter. If you want avoid collisions, just let each thread start with a different number (`0`, `1`, `2`, …) and increment by the number of threads. I.e. the first thread will use `0`, `16`, `32`, … the second thread will use `1`, `17`, `33`, … etc. The strings are even easier to solve as they have random content anyway. Just use one `Random` instance per thread and pass in the `index` as a parameter. – Holger Mar 03 '15 at 08:28
  • @Holger - Than you for advice, I am going to try your approach. It will be better then my code. – Alex Mar 03 '15 at 15:22

0 Answers0