scale up with lock-free algorithms @ javaone

107
Scale Up with Lock-Free Algorithms Non-blocking concurrency on JVM Presented at JavaOne 2017 /Roman Elizarov @ JetBrains

Upload: roman-elizarov

Post on 21-Jan-2018

258 views

Category:

Technology


0 download

TRANSCRIPT

Page 1: Scale Up with Lock-Free Algorithms @ JavaOne

ScaleUpwithLock-FreeAlgorithms

Non-blockingconcurrencyonJVMPresentedatJavaOne 2017/RomanElizarov@JetBrains

Page 2: Scale Up with Lock-Free Algorithms @ JavaOne

Speaker:RomanElizarov

• 16+yearsexperience• Previouslydevelopedhigh-perftradingsoftware@Devexperts• Teachconcurrent&[email protected]• Chiefjudge@NorthernEurasiaContest/ACMICPC• NowworkonKotlin@JetBrains

Page 3: Scale Up with Lock-Free Algorithms @ JavaOne

Shared

Page 4: Scale Up with Lock-Free Algorithms @ JavaOne

SharedMutable

Page 5: Scale Up with Lock-Free Algorithms @ JavaOne

SharedMutableState

Page 6: Scale Up with Lock-Free Algorithms @ JavaOne
Page 7: Scale Up with Lock-Free Algorithms @ JavaOne
Page 8: Scale Up with Lock-Free Algorithms @ JavaOne

SharedMutableState

Why?

Page 9: Scale Up with Lock-Free Algorithms @ JavaOne

BigBigData

Page 10: Scale Up with Lock-Free Algorithms @ JavaOne

Data1 Data2 DataN

Page 11: Scale Up with Lock-Free Algorithms @ JavaOne

Data1 Data2 DataN

map map map

Page 12: Scale Up with Lock-Free Algorithms @ JavaOne

Data1 Data2 DataN

map map map

reduce

answer

Page 13: Scale Up with Lock-Free Algorithms @ JavaOne

Embarrassinglyparallel

Data1 Data2 DataN

map map map

reduce

answer

Page 14: Scale Up with Lock-Free Algorithms @ JavaOne

BigBigData

Page 15: Scale Up with Lock-Free Algorithms @ JavaOne

BigBigDataReal-time

Page 16: Scale Up with Lock-Free Algorithms @ JavaOne

BigBigDataReal-time

Concurrentrequests/processing

Page 17: Scale Up with Lock-Free Algorithms @ JavaOne

BigBigDataReal-time

Concurrentrequests/processing

Performance Scalability

Page 18: Scale Up with Lock-Free Algorithms @ JavaOne
Page 19: Scale Up with Lock-Free Algorithms @ JavaOne

Atoyproblem

Page 20: Scale Up with Lock-Free Algorithms @ JavaOne

Atoyproblem– stack

Page 21: Scale Up with Lock-Free Algorithms @ JavaOne

Atoyproblem– stackclass Node<T>(val next: Node<T>?, val value: T)

Page 22: Scale Up with Lock-Free Algorithms @ JavaOne

public final class Node<T> {private final Node<T> next;private final T value;

public Node(Node<T> next, T value) {this.next = next;this.value = value;

}

public Node<T> getNext() {return next;

}

public T getValue() {return value;

}}

Atoyproblem– stack

Page 23: Scale Up with Lock-Free Algorithms @ JavaOne

Atoyproblem– stackclass Node<T>(val next: Node<T>?, val value: T)

Page 24: Scale Up with Lock-Free Algorithms @ JavaOne

Atoyproblem– emptystackclass Node<T>(val next: Node<T>?, val value: T)

top

Page 25: Scale Up with Lock-Free Algorithms @ JavaOne

Atoyproblem– stackpushclass Node<T>(val next: Node<T>?, val value: T)

next = nullvalue = 1

top А

Page 26: Scale Up with Lock-Free Algorithms @ JavaOne

Atoyproblem– stackpushclass Node<T>(val next: Node<T>?, val value: T)

next = nullvalue = 1

top А

next = Avalue = 2

B

Page 27: Scale Up with Lock-Free Algorithms @ JavaOne

Atoyproblem– stackpushclass Node<T>(val next: Node<T>?, val value: T)

next = nullvalue = 1

top А

next = Avalue = 2

B

Page 28: Scale Up with Lock-Free Algorithms @ JavaOne

Atoyproblem– stackpushclass Node<T>(val next: Node<T>?, val value: T)

next = Avalue = 2

top

next = nullvalue = 1

B A

Page 29: Scale Up with Lock-Free Algorithms @ JavaOne

Atoyproblem– stackpushclass Node<T>(val next: Node<T>?, val value: T)

class LinkedStack<T> {private var top: Node<T>? = null

fun push(value: T) {top = Node(top, value)

}}

Page 30: Scale Up with Lock-Free Algorithms @ JavaOne

Atoyproblem– stackpushclass Node<T>(val next: Node<T>?, val value: T)

class LinkedStack<T> {private var top: Node<T>? = null

fun push(value: T) {top = Node(top, value)

}}

Page 31: Scale Up with Lock-Free Algorithms @ JavaOne

Atoyproblem– stackpushclass Node<T>(val next: Node<T>?, val value: T)

class LinkedStack<T> {private var top: Node<T>? = null

fun push(value: T) {top = Node(top, value)

}}

Page 32: Scale Up with Lock-Free Algorithms @ JavaOne

Atoyproblem– stackclass Node<T>(val next: Node<T>?, val value: T)

next = Avalue = 2

top

next = nullvalue = 1

B A

Page 33: Scale Up with Lock-Free Algorithms @ JavaOne

Atoyproblem– stackpopclass Node<T>(val next: Node<T>?, val value: T)

next = Avalue = 2

top

next = nullvalue = 1

B A

cur

Page 34: Scale Up with Lock-Free Algorithms @ JavaOne

Atoyproblem– stackpopclass Node<T>(val next: Node<T>?, val value: T)

next = Avalue = 2

top

next = nullvalue = 1

B A

cur

Page 35: Scale Up with Lock-Free Algorithms @ JavaOne

Atoyproblem– stackpopclass Node<T>(val next: Node<T>?, val value: T)

next = Avalue = 2

top

next = nullvalue = 1

B A

curresult = 2

Page 36: Scale Up with Lock-Free Algorithms @ JavaOne

Atoyproblem– stackpopclass Node<T>(val next: Node<T>?, val value: T)

class LinkedStack<T> {private var top: Node<T>? = null

fun push(value: T) {top = Node(top, value)

}

fun pop(): T? {val cur = top ?: return nulltop = cur.nextreturn cur.value

}}

Page 37: Scale Up with Lock-Free Algorithms @ JavaOne

Atoyproblem– stackclass Node<T>(val next: Node<T>?, val value: T)

class LinkedStack<T> {private var top: Node<T>? = null

fun push(value: T) {top = Node(top, value)

}

fun pop(): T? {val cur = top ?: return nulltop = cur.nextreturn cur.value

}}

Page 38: Scale Up with Lock-Free Algorithms @ JavaOne

Doesitwork?

Page 39: Scale Up with Lock-Free Algorithms @ JavaOne

Atoyproblem– concurrentpushclass Node<T>(val next: Node<T>?, val value: T)

next = nullvalue = 1

top А

next = Avalue = 2

B

Page 40: Scale Up with Lock-Free Algorithms @ JavaOne

Atoyproblem– concurrentpushclass Node<T>(val next: Node<T>?, val value: T)

next = nullvalue = 1

top А

next = Avalue = 2

B

next = Avalue = 3

C

Page 41: Scale Up with Lock-Free Algorithms @ JavaOne

Atoyproblem– concurrentpushclass Node<T>(val next: Node<T>?, val value: T)

next = nullvalue = 1

top А

next = Avalue = 2

B

next = Avalue = 3

C

Page 42: Scale Up with Lock-Free Algorithms @ JavaOne

Atoyproblem– synchronizedstackclass Node<T>(val next: Node<T>?, val value: T)

class LinkedStack<T> {private var top: Node<T>? = null

@Synchronizedfun push(value: T) {

top = Node(top, value)}

@Synchronizedfun pop(): T? {

val cur = top ?: return nulltop = cur.nextreturn cur.value

}}

Page 43: Scale Up with Lock-Free Algorithms @ JavaOne

Doesitscale?

Page 44: Scale Up with Lock-Free Algorithms @ JavaOne

Benchmark@State(Scope.Benchmark)open class LinkedStackBenchmark {

private val stack = LinkedStack<Int>()

@Benchmarkfun benchmark() {

stack.push(1)check(stack.pop() == 1)

}}

Page 45: Scale Up with Lock-Free Algorithms @ JavaOne

Benchmark@State(Scope.Benchmark)open class LinkedStackBenchmark {

private val stack = LinkedStack<Int>()

@Benchmarkfun benchmark() {

stack.push(1)check(stack.pop() == 1)

}}

Page 46: Scale Up with Lock-Free Algorithms @ JavaOne

Benchmarkresults

0

5

10

15

20

25

1 2 4 8 16 32 64 128

Millions

Numberofthreads

Throughput(ops/s)

LinkedStack

Intel(R)Xeon(R)[email protected];32HWthreads;JavaHotSpot(TM)64-BitServerVM(build9+181,mixedmode)

Page 47: Scale Up with Lock-Free Algorithms @ JavaOne

Contention

P

Q

pop1

Page 48: Scale Up with Lock-Free Algorithms @ JavaOne

Contention

P

Q

pop1

Page 49: Scale Up with Lock-Free Algorithms @ JavaOne

Contention

P

Q

pop1

pop2wait

Page 50: Scale Up with Lock-Free Algorithms @ JavaOne

Contention

P

Q work

pop1

pop2wait

Page 51: Scale Up with Lock-Free Algorithms @ JavaOne

Deadlocks

Page 52: Scale Up with Lock-Free Algorithms @ JavaOne

Lock-free?

Page 53: Scale Up with Lock-Free Algorithms @ JavaOne

Lock-freepushclass Node<T>(val next: Node<T>?, val value: T)

next = nullvalue = 1

top А

next = Avalue = 2

B

expect

Page 54: Scale Up with Lock-Free Algorithms @ JavaOne

Lock-freepushclass Node<T>(val next: Node<T>?, val value: T)

next = nullvalue = 1

top А

next = Avalue = 2

Bupdate

Page 55: Scale Up with Lock-Free Algorithms @ JavaOne

Lock-freepushclass Node<T>(val next: Node<T>?, val value: T)

next = nullvalue = 1

top А

next = Avalue = 2

Bupdate

expect

Page 56: Scale Up with Lock-Free Algorithms @ JavaOne

AtomicReferencepackage java.util.concurrent.atomic;

/** @since 1.5 */public class AtomicReference<V> {

private volatile V value;

public V get() {return value;

}

public boolean compareAndSet(V expect, V update) {// …

}}

Page 57: Scale Up with Lock-Free Algorithms @ JavaOne

AtomicReferencepackage java.util.concurrent.atomic;

/** @since 1.5 */public class AtomicReference<V> {

private volatile V value;

public V get() {return value;

}

public boolean compareAndSet(V expect, V update) {// …

}}

Page 58: Scale Up with Lock-Free Algorithms @ JavaOne

AtomicReferencepackage java.util.concurrent.atomic;

/** @since 1.5 */public class AtomicReference<V> {

private volatile V value;

public V get() {return value;

}

public boolean compareAndSet(V expect, V update) {// …

}}

Page 59: Scale Up with Lock-Free Algorithms @ JavaOne

UsingAtomicReference

class LockFree<T> {private val top = AtomicReference<Node<T>?>(null)

fun push(value: T) {while (true) {

val cur = top.get()val upd = Node(cur, value)if (top.compareAndSet(cur, upd)) return

}}

}

Page 60: Scale Up with Lock-Free Algorithms @ JavaOne

UsingAtomicReference

class LockFree<T> {private val top = AtomicReference<Node<T>?>(null)

fun push(value: T) {while (true) {

val cur = top.get()val upd = Node(cur, value)if (top.compareAndSet(cur, upd)) return

}}

}

Page 61: Scale Up with Lock-Free Algorithms @ JavaOne

UsingAtomicReference - push

class LockFree<T> {private val top = AtomicReference<Node<T>?>(null)

fun push(value: T) {while (true) {

val cur = top.get()val upd = Node(cur, value)if (top.compareAndSet(cur, upd)) return

}}

}

1

Page 62: Scale Up with Lock-Free Algorithms @ JavaOne

UsingAtomicReference - push

class LockFree<T> {private val top = AtomicReference<Node<T>?>(null)

fun push(value: T) {while (true) {

val cur = top.get()val upd = Node(cur, value)if (top.compareAndSet(cur, upd)) return

}}

}

12

Page 63: Scale Up with Lock-Free Algorithms @ JavaOne

UsingAtomicReference

class LockFree<T> {private val top = AtomicReference<Node<T>?>(null)

fun push(value: T) {while (true) {

val cur = top.get()val upd = Node(cur, value)if (top.compareAndSet(cur, upd)) return

}}

}

123

Page 64: Scale Up with Lock-Free Algorithms @ JavaOne

UsingAtomicReference - push

123

class LockFree<T> {private val top = AtomicReference<Node<T>?>(null)

fun push(value: T) {while (true) {

val cur = top.get()val upd = Node(cur, value)if (top.compareAndSet(cur, upd)) return

}}

}

Page 65: Scale Up with Lock-Free Algorithms @ JavaOne

Powerfulwehavebecome!

Page 66: Scale Up with Lock-Free Algorithms @ JavaOne

UsingAtomicReference - pop

class LockFree<T> {private val top = AtomicReference<Node<T>?>(null)

fun push(value: T) { … }

fun pop(): T? {while (true) {

val cur = top.get() ?: return nullif (top.compareAndSet(cur, cur.next)) return cur.value

}}

}

Page 67: Scale Up with Lock-Free Algorithms @ JavaOne

UsingAtomicReference

class LockFree<T> {private val top = AtomicReference<Node<T>?>(null)

fun push(value: T) { … }

fun pop(): T? { … }}

Page 68: Scale Up with Lock-Free Algorithms @ JavaOne

It’satrap

Page 69: Scale Up with Lock-Free Algorithms @ JavaOne

Usingvolatilevariable

class LinkedStackLF<T> {@Volatileprivate var top: Node<T>? = null

fun push(value: T) {// ...

}}

Page 70: Scale Up with Lock-Free Algorithms @ JavaOne

UsingAtomicReferenceFieldUpdaterpackage java.util.concurrent.atomic;

/** @since 1.5 */public abstract class AtomicReferenceFieldUpdater<T,V> {

public static <U,W> AtomicReferenceFieldUpdater<U,W> newUpdater(Class<U> tclass, Class<W> vclass, String fieldName;

public abstract boolean compareAndSet(T obj, V expect, V update;}

Page 71: Scale Up with Lock-Free Algorithms @ JavaOne

UsingAtomicReferenceFieldUpdaterprivate volatile Node<T> top;

Page 72: Scale Up with Lock-Free Algorithms @ JavaOne

UsingAtomicReferenceFieldUpdaterprivate volatile Node<T> top;

private static final AtomicReferenceFieldUpdater<LockFree, Node> TOP = AtomicReferenceFieldUpdater

.newUpdater(LockFree.class, Node.class, "top");

Page 73: Scale Up with Lock-Free Algorithms @ JavaOne

UsingAtomicReferenceFieldUpdaterprivate volatile Node<T> top;

private static final AtomicReferenceFieldUpdater<LockFree, Node> TOP = AtomicReferenceFieldUpdater

.newUpdater(LockFree.class, Node.class, "top");

if (TOP.compareAndSet(this, cur, upd)) return;

Page 74: Scale Up with Lock-Free Algorithms @ JavaOne

UsingVarHandlepackage java.lang.invoke;

/** @since 9 */public abstract class VarHandle {

@MethodHandle.PolymorphicSignaturepublic native boolean compareAndSet(Object... args);

}

Page 75: Scale Up with Lock-Free Algorithms @ JavaOne

UsingVarHandleprivate volatile Node<T> top;

private static final VarHandle TOP;

static {try {

TOP = MethodHandles.lookup().findVarHandle(LockFree.class, "top", Node.class);

} catch (NoSuchFieldException | IllegalAccessException e) {throw new InternalError(e);

}}

Page 76: Scale Up with Lock-Free Algorithms @ JavaOne

UsingVarHandleprivate volatile Node<T> top;

private static final VarHandle TOP;

static {try {

TOP = MethodHandles.lookup().findVarHandle(LockFree.class, "top", Node.class);

} catch (NoSuchFieldException | IllegalAccessException e) {throw new InternalError(e);

}}

if (TOP.compareAndSet(this, cur, upd) return;

Page 77: Scale Up with Lock-Free Algorithms @ JavaOne

UsingAtomicFUJprivate val top = atomic<Node<T>?>(null)

Page 78: Scale Up with Lock-Free Algorithms @ JavaOne

UsingAtomicFUJprivate val top = atomic<Node<T>?>(null)

if (top.compareAndSet(cur, upd)) return

Page 79: Scale Up with Lock-Free Algorithms @ JavaOne

UsingAtomicFUJprivate val top = atomic<Node<T>?>(null)

if (top.compareAndSet(cur, upd)) return

CodelikeAtomicReference

Page 80: Scale Up with Lock-Free Algorithms @ JavaOne

UsingAtomicFUJprivate val top = atomic<Node<T>?>(null)

if (top.compareAndSet(cur, upd)) return

BytecodeCodelikeAtomicReference

compile

Page 81: Scale Up with Lock-Free Algorithms @ JavaOne

UsingAtomicFUJprivate val top = atomic<Node<T>?>(null)

if (top.compareAndSet(cur, upd)) return

BytecodeCodelikeAtomicReference AtomicReferenceFUcompile atomicFU

Page 82: Scale Up with Lock-Free Algorithms @ JavaOne

UsingAtomicFUJprivate val top = atomic<Node<T>?>(null)

if (top.compareAndSet(cur, upd)) return

BytecodeCodelikeAtomicReference VarHandlecompile atomicFU

Page 83: Scale Up with Lock-Free Algorithms @ JavaOne

Wasitworthit?

Page 84: Scale Up with Lock-Free Algorithms @ JavaOne

Benchmarkresults

0

5

10

15

20

25

30

35

40

1 2 4 8 16 32 64 128

Millions

Numberofthreads

Throughput(ops/s)

LockFree

LinkedStack

Page 85: Scale Up with Lock-Free Algorithms @ JavaOne

0

5

10

15

20

25

30

35

40

1 2 4 8 16 32 64 128

Millions

Numberofthreads

Throughput(ops/s)

LockFree

LinkedStack

Benchmarkresults

Yeh!

Nay…

Page 86: Scale Up with Lock-Free Algorithms @ JavaOne

Contention

P

Q retry

pop1

pop2tryupdate

Page 87: Scale Up with Lock-Free Algorithms @ JavaOne

Tootoyofaproblem?class LinkedStack<T> {

private var top: Node<T>? = null

@Synchronizedfun push(value: T) { … }

@Synchronizedfun pop(): T? {

val cur = top ?: return nulltop = cur.nextreturn cur.value

}}

Page 88: Scale Up with Lock-Free Algorithms @ JavaOne

Tootoyofaproblem– makeitmore real?class LinkedStack<T> {

private var top: Node<T>? = null

@Synchronizedfun push(value: T) { … }

@Synchronizedfun pop(): T? {

val cur = top ?: return nulltop = cur.nextBlackhole.consumeCPU(100L) return cur.value

}}

Page 89: Scale Up with Lock-Free Algorithms @ JavaOne

Tootoyofaproblem– makeitmore real?class LockFree<T> {

private val top = atomic<Node<T>?>(null)

fun push(value: T) { … }

fun pop(): T? {while (true) {

val cur = top.value ?: return nullBlackhole.consumeCPU(100L)if (top.compareAndSet(cur, cur.next)) return cur.value

}}

}

Page 90: Scale Up with Lock-Free Algorithms @ JavaOne

Benchmarkresults

00.51

1.52

2.53

3.54

4.5

1 2 4 8 16 32 64 128

Millions

Numberofthreads

Throughput(ops/s)

LockFree

LinkedStack

Page 91: Scale Up with Lock-Free Algorithms @ JavaOne

Workload@State(Scope.Benchmark)open class LinkedStackBenchmark {

private val stack = LinkedStack<Int>()

@Benchmarkfun benchmark() {

stack.push(1)check(stack.pop() == 1)

}}

Page 92: Scale Up with Lock-Free Algorithms @ JavaOne

Read-dominatedworkload@State(Scope.Benchmark)open class LinkedStackBenchmark {

private val stack = LinkedStack<Int>()

@Benchmarkfun benchmarkReadDominated() {

stack.push(1)repeat(10) { check(stack.peek() == 1) }check(stack.pop() == 1)

}}

Page 93: Scale Up with Lock-Free Algorithms @ JavaOne

Read-dominatedworkload@State(Scope.Benchmark)open class LinkedStackBenchmark {

private val stack = LinkedStack<Int>()

@Benchmarkfun benchmarkReadDominated() {

stack.push(1)repeat(10) { check(stack.peek() == 1) }check(stack.pop() == 1)

}}

class LinkedStack<T> {@Synchronizedfun peek() = top?.value

}

Page 94: Scale Up with Lock-Free Algorithms @ JavaOne

Read-dominatedworkload@State(Scope.Benchmark)open class LockFreeBenchmark {

private val stack = LockFree<Int>()

@Benchmarkfun benchmarkReadDominated() {

stack.push(1)repeat(10) { check(stack.peek() == 1) }check(stack.pop() == 1)

}}

class LockFree<T> {fun peek() = top.value?.value

}

Page 95: Scale Up with Lock-Free Algorithms @ JavaOne

Benchmarkresults– x10reads

0

5

10

15

20

25

1 2 4 8 16 32 64 128

Millions

Numberofthreads

Throughput(ops/s)

LockFree

LinkedStack

Page 96: Scale Up with Lock-Free Algorithms @ JavaOne

Benchmarkresults– x100reads

0

1

2

3

4

5

6

1 2 4 8 16 32 64 128

Millions

Numberofthreads

Throughput(ops/s)

LockFree

LinkedStack

Page 97: Scale Up with Lock-Free Algorithms @ JavaOne

But… scalability?

Page 98: Scale Up with Lock-Free Algorithms @ JavaOne

Real-worldworkload@Benchmarkfun benchmarkReadWorld() {

stack.push(1)repeat(10) {

check(stack.peek() == 1)Blackhole.consumeCPU(100L)

}check(stack.pop() == 1)Blackhole.consumeCPU(100L)

}

Page 99: Scale Up with Lock-Free Algorithms @ JavaOne

Benchmarkresults– realworld

00.20.40.60.81

1.21.41.61.8

1 2 4 8 16 32 64 128

Millions

Numberofthreads

Throughput(ops/s)

LockFree

LinkedStack

Page 100: Scale Up with Lock-Free Algorithms @ JavaOne

Learntoasktherightquestions

Youshall,youngPadawan.

Page 101: Scale Up with Lock-Free Algorithms @ JavaOne
Page 102: Scale Up with Lock-Free Algorithms @ JavaOne

Links

• JMH http://openjdk.java.net/projects/code-tools/jmh/• Kotlin https://kotlinlang.org• AtomicFU https://github.com/Kotlin/kotlinx.atomicfu

Page 103: Scale Up with Lock-Free Algorithms @ JavaOne

Thankyou

Anyquestions?

Slidesareavailableatwww.slideshare.net/elizarovemailmetoelizarov atgmail

relizarov

Page 104: Scale Up with Lock-Free Algorithms @ JavaOne

Appendix

Page 105: Scale Up with Lock-Free Algorithms @ JavaOne

Atoyproblem– concurrentpopclass Node<T>(val next: Node<T>?, val value: T)

next = Avalue = 2

top

next = nullvalue = 1

B A

cur1 cur2

Page 106: Scale Up with Lock-Free Algorithms @ JavaOne

Atoyproblem– concurrentpopclass Node<T>(val next: Node<T>?, val value: T)

next = Avalue = 2

top

next = nullvalue = 1

B A

cur1 cur2

Page 107: Scale Up with Lock-Free Algorithms @ JavaOne

Atoyproblem– concurrentpopclass Node<T>(val next: Node<T>?, val value: T)

next = Avalue = 2

top

next = nullvalue = 1

B A

result1 = 2cur1 cur2

result2 = 2