Skip to content

Instantly share code, notes, and snippets.

@Palmr
Last active July 28, 2024 22:15
Show Gist options
  • Save Palmr/a1ee2df22cb26ff0dea4be9cdfefee02 to your computer and use it in GitHub Desktop.
Save Palmr/a1ee2df22cb26ff0dea4be9cdfefee02 to your computer and use it in GitHub Desktop.
JMH Bench for difference between readers of volatile or non-volatile data
package com.lmax.disruptor;
import net.openhft.affinity.Affinity;
import net.openhft.affinity.AffinityLock;
import org.openjdk.jmh.annotations.Benchmark;
import org.openjdk.jmh.annotations.BenchmarkMode;
import org.openjdk.jmh.annotations.Fork;
import org.openjdk.jmh.annotations.Measurement;
import org.openjdk.jmh.annotations.Mode;
import org.openjdk.jmh.annotations.OutputTimeUnit;
import org.openjdk.jmh.annotations.Scope;
import org.openjdk.jmh.annotations.Setup;
import org.openjdk.jmh.annotations.State;
import org.openjdk.jmh.annotations.TearDown;
import org.openjdk.jmh.annotations.Threads;
import org.openjdk.jmh.annotations.Warmup;
import org.openjdk.jmh.infra.Blackhole;
import org.openjdk.jmh.runner.Runner;
import org.openjdk.jmh.runner.RunnerException;
import org.openjdk.jmh.runner.options.Options;
import org.openjdk.jmh.runner.options.OptionsBuilder;
import java.util.Arrays;
import java.util.List;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.stream.Collectors;
import static java.util.function.Predicate.not;
@BenchmarkMode(Mode.Throughput)
@OutputTimeUnit(TimeUnit.MICROSECONDS)
@Warmup(iterations = 10, time = 1)
@Measurement(iterations = 10, time = 1)
@Fork(2)
@Threads(1)
@State(Scope.Thread)
public class VolatileBenchmark
{
// To run this on a tuned system with benchmark threads pinned to isolated cpus:
// Run the JMH process with an env var defining the isolated cpu list, e.g. ISOLATED_CPUS=38,40,42,44,46,48 java -jar disruptor-jmh.jar
private static final List<Integer> ISOLATED_CPUS = Arrays.stream(System.getenv().getOrDefault("ISOLATED_CPUS", "").split(","))
.map(String::trim)
.filter(not(String::isBlank))
.map(Integer::valueOf)
.collect(Collectors.toList());
private static final AtomicInteger THREAD_COUNTER = new AtomicInteger();
@State(Scope.Thread)
public static class ThreadPinningState
{
int threadId = THREAD_COUNTER.getAndIncrement();
private AffinityLock affinityLock;
@Setup
public void setup()
{
affinityLock = isolatedThreadPin(threadId);
}
@TearDown
public void teardown()
{
if (ISOLATED_CPUS.size() > 0)
{
affinityLock.release();
}
}
}
private static final int WRITER_THREAD_ID = THREAD_COUNTER.getAndIncrement();
private AffinityLock writerThreadAffinityLock;
private volatile boolean writerRunning;
private long nonVolatileLong;
private volatile long volatileLong;
@SuppressWarnings("NonAtomicOperationOnVolatileField")
@Setup
public void setup() throws InterruptedException
{
final CountDownLatch writerStartedLatch = new CountDownLatch(1);
Thread writerThread = new Thread(() ->
{
writerThreadAffinityLock = isolatedThreadPin(WRITER_THREAD_ID);
writerStartedLatch.countDown();
while (writerRunning)
{
nonVolatileLong += 1;
volatileLong += 1;
}
});
writerThread.setDaemon(true);
writerThread.setName("WriterThread");
writerRunning = true;
writerThread.start();
writerStartedLatch.await();
}
@Benchmark
public void readingNonVolatile(final Blackhole bh, final ThreadPinningState t)
{
bh.consume(nonVolatileLong);
}
@Benchmark
public void readingVolatile(final Blackhole bh, final ThreadPinningState t)
{
bh.consume(volatileLong);
}
@TearDown
public void tearDown()
{
writerRunning = false;
if (writerThreadAffinityLock != null)
{
writerThreadAffinityLock.release();
}
}
private static AffinityLock isolatedThreadPin(final int threadId)
{
if (ISOLATED_CPUS.size() > 0)
{
if (threadId > ISOLATED_CPUS.size())
{
throw new IllegalArgumentException(
String.format("Benchmark uses at least %d threads, only defined %d isolated cpus",
threadId,
ISOLATED_CPUS.size()
));
}
final Integer cpuId = ISOLATED_CPUS.get(threadId);
final AffinityLock affinityLock = AffinityLock.acquireLock(cpuId);
System.out.printf("Attempted to set thread affinity for %s to %d, success = %b%n",
Thread.currentThread().getName(),
cpuId,
affinityLock.isAllocated()
);
return affinityLock;
}
else
{
System.err.printf("ISOLATED_CPUS environment variable not defined, running thread %s (id=%d) on scheduler-defined CPU:%d%n ",
Thread.currentThread().getName(),
threadId,
Affinity.getCpu());
}
return null;
}
public static void main(final String[] args) throws RunnerException
{
Options opt = new OptionsBuilder()
.include(VolatileBenchmark.class.getSimpleName())
.forks(1)
.build();
new Runner(opt).run();
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment