Last active
December 1, 2016 09:14
-
-
Save Gikkman/879a8be3d86be6cc78b453b7b05cbbcd to your computer and use it in GitHub Desktop.
A test I wrote to compare read and write speeds of HashMap and ConcurrentHashMap.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| import java.util.HashMap; | |
| import java.util.LinkedList; | |
| import java.util.List; | |
| import java.util.Map; | |
| import java.util.Random; | |
| import java.util.concurrent.ConcurrentHashMap; | |
| import java.util.concurrent.ThreadLocalRandom; | |
| public class ReadWriteTimeTest | |
| { | |
| private static long SAFE = 0L; | |
| private static final int MAP_MAX_SIZE = 1000000; | |
| private static final int WRITE_ACTIONS = 1 * 1000 * 1000; | |
| private static final int READ_ACTIONS = 1 * 1000 * 1000; | |
| private static final int THREADS = 10; | |
| public static void main(String... args) throws InterruptedException | |
| { | |
| // Test for different amounts of concurrency; from 1 thread up to THREADS. | |
| // This will show how different collections perform under different circumstances | |
| for (int i = 1; i <= THREADS; i++) | |
| { | |
| Map<Integer, Long> globalCache; | |
| List<Thread> clients; | |
| System.out.println("*********************************************************************************************"); | |
| System.out.println("* Test config: MAP_MAX_SIZE=" + MAP_MAX_SIZE + " WRITE_ACTIONS=" + WRITE_ACTIONS + " READ_ACTIONS=" + READ_ACTIONS + " THREADS=" + i); | |
| System.out.println("*********************************************************************************************"); | |
| System.out.println(); | |
| SAFE = 0; | |
| globalCache = new ConcurrentHashMap<>(); | |
| System.out.println(" [ConcurrentHashMap]"); | |
| clients = createThreads(i, writeTestRunnable(globalCache)); | |
| runThreads("WriteTest", "ConcurrentHashMap", clients); | |
| fillCache(globalCache); | |
| clients = createThreads(i, readTestRunnable(globalCache)); | |
| runThreads("ReadTest", "ConcurrentHashMap", clients); | |
| System.out.println(); | |
| SAFE = 0; | |
| globalCache = new HashMap<>(); | |
| System.out.println(" [HashMap]"); | |
| clients = createThreads(i, writeTestRunnable(globalCache)); | |
| runThreads("WriteTest", "HashMap", clients); | |
| fillCache(globalCache); | |
| clients = createThreads(i, readTestRunnable(globalCache)); | |
| runThreads("ReadTest", "HashMap", clients); | |
| System.out.println(); | |
| } | |
| System.out.println("Avoid compiler optimization: " + SAFE); | |
| } | |
| private static Runnable readTestRunnable(final Map<Integer, Long> globalCache) | |
| { | |
| // This runnable simulates a lot of read requests coming in on different threads, reading from the cache. | |
| return new Runnable() { | |
| @Override | |
| public void run() | |
| { | |
| try | |
| { | |
| // Generate random numbers, representing the cache key | |
| Random rng = ThreadLocalRandom.current(); | |
| for (int i = 0; i < READ_ACTIONS && !Thread.interrupted(); i++) | |
| { | |
| // This operation is simply to avoid the compiler from optimizing away our method call | |
| SAFE += globalCache.get(rng.nextInt(MAP_MAX_SIZE)); | |
| } | |
| } | |
| catch (Exception e) | |
| { | |
| System.out.println("\t" + Thread.currentThread().getName() + " - Read Exception: " + e); | |
| } | |
| } | |
| }; | |
| } | |
| private static Runnable writeTestRunnable(final Map<Integer, Long> globalCache) | |
| { | |
| // This runnable simulates a lot of write requests coming in on different threads, writing to the cache. | |
| return new Runnable() { | |
| @Override | |
| public void run() | |
| { | |
| try | |
| { | |
| // If a HashMap is passed to this method, give each thread their own HashMap | |
| final Map<Integer, Long> localCache = globalCache instanceof HashMap ? new HashMap<Integer, Long>() : globalCache; | |
| // Generate random numbers, representing the cache key | |
| Random rng = ThreadLocalRandom.current(); | |
| for (int i = 0; i < WRITE_ACTIONS && !Thread.interrupted(); i++) | |
| { | |
| // TODO: Switch 'localCache' to 'globalCache' if you want to see what happens when a lot of | |
| // threads edits the same HashMap concurrently | |
| localCache.put(rng.nextInt(MAP_MAX_SIZE), rng.nextLong()); | |
| } | |
| } | |
| catch (Exception e) | |
| { | |
| System.out.println("\t" + Thread.currentThread().getName() + " - Write Exception: " + e); | |
| } | |
| } | |
| }; | |
| } | |
| private static void fillCache(Map<Integer, Long> globalCache) | |
| { | |
| // Simply fills the map from index 0 to index MAX_MAP_SIZE, so that there | |
| // aren't any holes when we do the read test | |
| try | |
| { | |
| for (int i = 0; i < MAP_MAX_SIZE; i++) | |
| { | |
| globalCache.put(i, (long) i); | |
| } | |
| } | |
| catch (Exception e) | |
| { | |
| System.out.println("Filling map failed: " + e); | |
| } | |
| } | |
| private static List<Thread> createThreads(int nrThreads, Runnable runnable) | |
| { | |
| List<Thread> list = new LinkedList<>(); | |
| for (int i = 0; i < nrThreads; i++) | |
| { | |
| list.add(new Thread(runnable)); | |
| } | |
| return list; | |
| } | |
| @SuppressWarnings("deprecation") | |
| private static void runThreads(String testName, String mapType, List<Thread> threads) throws InterruptedException | |
| { | |
| System.out.println(testName + " started"); | |
| long start = System.currentTimeMillis(); | |
| for (Thread t : threads) | |
| { | |
| t.start(); | |
| } | |
| for (Thread t : threads) | |
| { | |
| // In case regular HashMaps writes to the same globalCache map, the threads risks breaking. | |
| // Therefore, I have this code in place to clean them up | |
| t.join(10 * 1000); | |
| t.stop(); | |
| } | |
| long stop = System.currentTimeMillis(); | |
| System.out.println(testName + " time: " + (stop - start) + "ms [" + mapType + "]"); | |
| } | |
| } |
Author
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
It turned out that having multiple threads writing to different keys in a regular HashMap didn't really work out, thus the somewhat strange solution on row 90.
If you still want to test what can potentially happen when multiple threads write to the same HashMap concurrently, switch row 99 to
globalCache.putinstead, so that each thread puts data into the same HashMap concurrently.