// Copyright Vespa.ai. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. package com.yahoo.concurrent; import java.util.List; import java.util.concurrent.atomic.AtomicInteger; /** * Benchmark to compare ThreadLocalDirectory with java.util.concurrent's atomic * variables. Very low precision since it's an adapted unit test. * * @author Steinar Knutsen */ public class ThreadLocalDirectoryBenchmark { private static final int ITERATIONS = 500000; private final AtomicInteger atomicCounter = new AtomicInteger(0); private volatile int volatileCounter = 0; private int naiveCounter = 0; private static class SumUpdater implements ThreadLocalDirectory.Updater { @Override public Integer update(Integer current, Integer x) { return Integer.valueOf(current.intValue() + x.intValue()); } @Override public Integer createGenerationInstance(Integer previous) { return Integer.valueOf(0); } } private static class Counter implements Runnable { ThreadLocalDirectory r; Counter(ThreadLocalDirectory r) { this.r = r; } @Override public void run() { LocalInstance s = r.getLocalInstance(); for (int i = 0; i < ITERATIONS; ++i) { r.update(Integer.valueOf(i), s); } } } private static class MutableSumUpdater implements ThreadLocalDirectory.Updater { @Override public IntWrapper update(IntWrapper current, IntWrapper x) { current.counter += x.counter; return current; } @Override public IntWrapper createGenerationInstance(IntWrapper previous) { return new IntWrapper(); } } private static class IntWrapper { public int counter = 0; } private static class WrapperCounter implements Runnable { ThreadLocalDirectory r; WrapperCounter(ThreadLocalDirectory r) { this.r = r; } @Override public void run() { LocalInstance s = r.getLocalInstance(); IntWrapper w = new IntWrapper(); for (int i = 0; i < ITERATIONS; ++i) { w.counter = i; r.update(w, s); } } } private class AtomicCounter implements Runnable { @Override public void run() { for (int i = 0; i < ITERATIONS; ++i) { atomicCounter.addAndGet(i); } } } /** * This just bangs on a shared volatile to give an idea of the basic cost of * sharing a single variable with a memory barrier. */ private class VolatileSillyness implements Runnable { @Override public void run() { for (int i = 0; i < ITERATIONS; ++i) { volatileCounter += i; } } } /** * This just bangs on a shared to give some sort of lower bound for time * elapsed. */ private class SillySillyness implements Runnable { @Override public void run() { for (int i = 0; i < ITERATIONS; ++i) { naiveCounter += i; } } } private void sumFromMultipleThreads() { SumUpdater updater = new SumUpdater(); ThreadLocalDirectory s = new ThreadLocalDirectory(updater); Thread[] threads = new Thread[500]; for (int i = 0; i < 500; ++i) { Counter c = new Counter(s); threads[i] = new Thread(c); } runAll(threads); List measurements = s.fetch(); long sum = 0; for (Integer i : measurements) { sum += i.intValue(); } System.out.println("Sum from all threads: " + sum); } private void sumMutableFromMultipleThreads() { MutableSumUpdater updater = new MutableSumUpdater(); ThreadLocalDirectory s = new ThreadLocalDirectory(updater); Thread[] threads = new Thread[500]; for (int i = 0; i < 500; ++i) { WrapperCounter c = new WrapperCounter(s); threads[i] = new Thread(c); } runAll(threads); List measurements = s.fetch(); long sum = 0; for (IntWrapper i : measurements) { sum += i.counter; } System.out.println("Sum from all threads: " + sum); } private void sumAtomicFromMultipleThreads() { Thread[] threads = new Thread[500]; for (int i = 0; i < 500; ++i) { AtomicCounter c = new AtomicCounter(); threads[i] = new Thread(c); } runAll(threads); System.out.println("Sum from all threads: " + atomicCounter.get()); } private void overwriteVolatileFromMultipleThreads() { Thread[] threads = new Thread[500]; for (int i = 0; i < 500; ++i) { VolatileSillyness c = new VolatileSillyness(); threads[i] = new Thread(c); } runAll(threads); System.out.println("Checksum from all threads: " + volatileCounter); } private void overwriteIntegerFromMultipleThreads() { Thread[] threads = new Thread[500]; for (int i = 0; i < 500; ++i) { SillySillyness c = new SillySillyness(); threads[i] = new Thread(c); } runAll(threads); System.out.println("Checksum from all threads: " + volatileCounter); } private void runAll(Thread[] threads) { for (Thread t : threads) { t.start(); } for (Thread t : threads) { try { t.join(); } catch (InterruptedException e) { // nop } } } public static void main(String[] args) { ThreadLocalDirectoryBenchmark benchmark = new ThreadLocalDirectoryBenchmark(); long end; System.out.println("ThreadLocalDirectory"); long start = System.currentTimeMillis(); benchmark.sumFromMultipleThreads(); end = System.currentTimeMillis(); System.out.println("Elapsed using threadlocals: " + (end - start) + " ms."); System.out.println("AtomicInteger"); start = System.currentTimeMillis(); benchmark.sumAtomicFromMultipleThreads(); end = System.currentTimeMillis(); System.out.println("Elapsed using atomic integer: " + (end - start) + " ms."); System.out.println("volatile int += volatile int"); start = System.currentTimeMillis(); benchmark.overwriteVolatileFromMultipleThreads(); end = System.currentTimeMillis(); System.out.println("Elapsed using single shared volatile: " + (end - start) + " ms."); System.out.println("int += int"); start = System.currentTimeMillis(); benchmark.overwriteIntegerFromMultipleThreads(); end = System.currentTimeMillis(); System.out.println("Checksum: " + benchmark.naiveCounter); System.out.println("Elapsed using shared int: " + (end - start) + " ms."); System.out.println("ThreadLocalDirectory"); start = System.currentTimeMillis(); benchmark.sumMutableFromMultipleThreads(); end = System.currentTimeMillis(); System.out.println("Elapsed using threadlocal with mutable int wrapper: " + (end - start) + " ms."); } }