У меня есть тест многопоточной JMH:Java безблокировочного производительность JMH
@State(Scope.Benchmark)
@BenchmarkMode(Mode.Throughput)
@OutputTimeUnit(TimeUnit.MICROSECONDS)
@Fork(value = 1, jvmArgsAppend = { "-Xmx512m", "-server", "-XX:+AggressiveOpts","-XX:+UnlockDiagnosticVMOptions",
"-XX:+UnlockExperimentalVMOptions", "-XX:+PrintAssembly", "-XX:PrintAssemblyOptions=intel",
"-XX:+PrintSignatureHandlers"})
@Measurement(iterations = 5, time = 5, timeUnit = TimeUnit.SECONDS)
@Warmup(iterations = 3, time = 2, timeUnit = TimeUnit.SECONDS)
public class LinkedQueueBenchmark {
private static final Unsafe unsafe = UnsafeProvider.getUnsafe();
private static final long offsetObject;
private static final long offsetNext;
private static final int THREADS = 5;
private static class Node {
private volatile Node next;
public Node() {}
}
static {
try {
offsetObject = unsafe.objectFieldOffset(LinkedQueueBenchmark.class.getDeclaredField("object"));
offsetNext = unsafe.objectFieldOffset(Node.class.getDeclaredField("next"));
} catch (Exception ex) { throw new Error(ex); }
}
protected long t0,t1,t2,t3,t4,t5,t6,t7;
private volatile Node object = new Node(null);
@Threads(THREADS)
@Benchmark
public Node doTestCasSmart() {
Node current, o = new Node();
for(;;) {
current = this.object;
if (unsafe.compareAndSwapObject(this, offsetObject, current, o)) {
//current.next = o; //Special line:
break;
} else {
LockSupport.parkNanos(1);
}
}
return current;
}
}
- В текущем варианте у меня есть производительность ~ 55 OPS/нас
- Но, если я раскомментировать «Специальную линию» , или замените его на unsafe.putOrderedObject (в любом направлении - current.next = o или o.next = текущий), производительность ~ 2 ops/us.
Как я понимаю, это происходит с CPU-кэшами и, возможно, это очистка буфера хранилища. Если я заменил его на метод блокировки, без CAS, производительность будет 11-20 ops/us.
Я пытаюсь использовать LinuxPerfAsmProfiler и PrintAssembly, во втором случае я вижу:
....[Hottest Regions]...............................................................................
25.92% 17.93% [0x7f1d5105fe60:0x7f1d5105fe69] in SpinPause (libjvm.so)
17.53% 20.62% [0x7f1d5119dd88:0x7f1d5119de57] in ParMarkBitMap::live_words_in_range(HeapWord*, oopDesc*) const (libjvm.so)
10.81% 6.30% [0x7f1d5129cff5:0x7f1d5129d0ed] in ParallelTaskTerminator::offer_termination(TerminatorTerminator*) (libjvm.so)
7.99% 9.86% [0x7f1d3c51d280:0x7f1d3c51d3a2] in com.jad.generated.LinkedQueueBenchmark_doTestCasSmart::doTestCasSmart_thrpt_jmhStub
Может кто-нибудь объяснить мне, что на самом деле произошло? Почему это так медленно? Где здесь барьер для хранения? Почему putOrdered не работает? И как это исправить?
Спасибо, Алексей. –
Я действительно не исследовал его на GC perf. Да, этот тест суррогатный. Я пытаюсь создать LIFO на основе CAS. У меня есть моя простая реализация, где метод * offer() * всегда получает текущий хвост (он всегда не равен нулю), CAS заменяет новый хвост и orderd put * next * в предыдущем хвосте. Профилер сказал, что этот метод является самым медленным. Я имею равную производительность с ConcurrentLinkedQueue, когда писатели <= читатели, и хуже в другом случае. Возможно, это тоже на GC. –