Last active
July 28, 2024 22:15
-
-
Save Palmr/a1ee2df22cb26ff0dea4be9cdfefee02 to your computer and use it in GitHub Desktop.
JMH Bench for difference between readers of volatile or non-volatile data
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
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