From d1a7ea2ed3f184877cc06f436208a04f4acdb1f9 Mon Sep 17 00:00:00 2001 From: LepilkinaElena Date: Fri, 24 Jan 2020 17:11:16 +0300 Subject: [PATCH] Benchmark with atomics (#3782) --- .../kotlin-jvm/org/jetbrains/ring/Utils.kt | 87 +++++++++++++++++- .../kotlin-native/org/jetbrains/ring/Utils.kt | 38 +++++++- performance/ring/src/main/kotlin/main.kt | 3 +- .../ring/LinkedListWithAtomicsBenchmark.kt | 88 +++++++++++++++++++ .../main/kotlin/org/jetbrains/ring/Utils.kt | 23 +++++ 5 files changed, 236 insertions(+), 3 deletions(-) create mode 100644 performance/ring/src/main/kotlin/org/jetbrains/ring/LinkedListWithAtomicsBenchmark.kt diff --git a/performance/ring/src/main/kotlin-jvm/org/jetbrains/ring/Utils.kt b/performance/ring/src/main/kotlin-jvm/org/jetbrains/ring/Utils.kt index 6f475fcb238..1be74679e54 100644 --- a/performance/ring/src/main/kotlin-jvm/org/jetbrains/ring/Utils.kt +++ b/performance/ring/src/main/kotlin-jvm/org/jetbrains/ring/Utils.kt @@ -15,6 +15,10 @@ */ package org.jetbrains.ring + +import java.util.concurrent.atomic.AtomicReferenceFieldUpdater +import java.util.concurrent.locks.ReentrantLock + //-----------------------------------------------------------------------------// actual class Random actual constructor() { @@ -31,4 +35,85 @@ actual class Random actual constructor() { return seedDouble } } -} \ No newline at end of file +} + +internal var interceptor: AtomicOperationInterceptor = DefaultInterceptor + private set +private val interceptorLock = ReentrantLock() + +internal fun lockAndSetInterceptor(impl: AtomicOperationInterceptor) { + if (!interceptorLock.tryLock() || interceptor !== DefaultInterceptor) { + error("Interceptor is locked by another test: $interceptor") + } + interceptor = impl +} + +internal fun unlockAndResetInterceptor(impl: AtomicOperationInterceptor) { + check(interceptor === impl) { "Unexpected interceptor found: $interceptor" } + interceptor = DefaultInterceptor + interceptorLock.unlock() +} + +/** + * Interceptor for modifications of atomic variables. + */ +internal open class AtomicOperationInterceptor { + open fun beforeUpdate(ref: AtomicRef) {} + open fun afterSet(ref: AtomicRef, newValue: T) {} + open fun afterRMW(ref: AtomicRef, oldValue: T, newValue: T) {} +} + +private object DefaultInterceptor : AtomicOperationInterceptor() { + override fun toString(): String = "DefaultInterceptor" +} + +@Suppress("UNCHECKED_CAST") +public actual class AtomicRef internal constructor(value: T) { + /** + * Reading/writing this property maps to read/write of volatile variable. + */ + @Volatile + public actual var value: T = value + set(value) { + interceptor.beforeUpdate(this) + field = value + interceptor.afterSet(this, value) + } + + /** + * Maps to [AtomicReferenceFieldUpdater.lazySet]. + */ + public actual fun lazySet(value: T) { + interceptor.beforeUpdate(this) + FU.lazySet(this, value) + interceptor.afterSet(this, value) + } + + /** + * Maps to [AtomicReferenceFieldUpdater.compareAndSet]. + */ + public actual fun compareAndSet(expect: T, update: T): Boolean { + interceptor.beforeUpdate(this) + val result = FU.compareAndSet(this, expect, update) + if (result) interceptor.afterRMW(this, expect, update) + return result + } + + /** + * Maps to [AtomicReferenceFieldUpdater.getAndSet]. + */ + public actual fun getAndSet(value: T): T { + interceptor.beforeUpdate(this) + val oldValue = FU.getAndSet(this, value) as T + interceptor.afterRMW(this, oldValue, value) + return oldValue + } + + override fun toString(): String = value.toString() + + private companion object { + private val FU = AtomicReferenceFieldUpdater.newUpdater(AtomicRef::class.java, Any::class.java, "value") + } +} + +public actual fun atomic(initial: T): AtomicRef = AtomicRef(initial) \ No newline at end of file diff --git a/performance/ring/src/main/kotlin-native/org/jetbrains/ring/Utils.kt b/performance/ring/src/main/kotlin-native/org/jetbrains/ring/Utils.kt index 4252b9954b3..3beb14e75f2 100644 --- a/performance/ring/src/main/kotlin-native/org/jetbrains/ring/Utils.kt +++ b/performance/ring/src/main/kotlin-native/org/jetbrains/ring/Utils.kt @@ -16,6 +16,10 @@ package org.jetbrains.ring +import kotlin.native.concurrent.FreezableAtomicReference as KAtomicRef +import kotlin.native.concurrent.isFrozen +import kotlin.native.concurrent.freeze + //-----------------------------------------------------------------------------// actual class Random actual constructor() { @@ -33,4 +37,36 @@ actual class Random actual constructor() { return seedDouble } } -} \ No newline at end of file +} + +public actual class AtomicRef constructor(@PublishedApi internal val a: KAtomicRef) { + public actual inline var value: T + get() = a.value + set(value) { + if (a.isFrozen) value.freeze() + a.value = value + } + + public actual inline fun lazySet(value: T) { + if (a.isFrozen) value.freeze() + a.value = value + } + + public actual inline fun compareAndSet(expect: T, update: T): Boolean { + if (a.isFrozen) update.freeze() + return a.compareAndSet(expect, update) + } + + public actual fun getAndSet(value: T): T { + if (a.isFrozen) value.freeze() + while (true) { + val cur = a.value + if (cur === value) return cur + if (a.compareAndSwap(cur, value) === cur) return cur + } + } + + override fun toString(): String = value.toString() +} + +public actual fun atomic(initial: T): AtomicRef = AtomicRef(KAtomicRef(initial)) \ No newline at end of file diff --git a/performance/ring/src/main/kotlin/main.kt b/performance/ring/src/main/kotlin/main.kt index c9d7f1582ce..7ff25abc7e6 100644 --- a/performance/ring/src/main/kotlin/main.kt +++ b/performance/ring/src/main/kotlin/main.kt @@ -205,7 +205,8 @@ class RingLauncher : Launcher() { "GraphSolver.solve" to BenchmarkEntryWithInit.create(::GraphSolverBenchmark, { solve() }), "Casts.classCast" to BenchmarkEntryWithInit.create(::CastsBenchmark, { classCast() }), "Casts.interfaceCast" to BenchmarkEntryWithInit.create(::CastsBenchmark, { interfaceCast() }), - "LocalObjects.localArray" to BenchmarkEntryWithInit.create(::LocalObjectsBenchmark, { localArray() }) + "LocalObjects.localArray" to BenchmarkEntryWithInit.create(::LocalObjectsBenchmark, { localArray() }), + "LinkedListWithAtomicsBenchmark" to BenchmarkEntryWithInit.create(::LinkedListWithAtomicsBenchmark, { ensureNext() }) ) ) } diff --git a/performance/ring/src/main/kotlin/org/jetbrains/ring/LinkedListWithAtomicsBenchmark.kt b/performance/ring/src/main/kotlin/org/jetbrains/ring/LinkedListWithAtomicsBenchmark.kt new file mode 100644 index 00000000000..24fcdc7ae90 --- /dev/null +++ b/performance/ring/src/main/kotlin/org/jetbrains/ring/LinkedListWithAtomicsBenchmark.kt @@ -0,0 +1,88 @@ +/* + * Copyright 2010-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license + * that can be found in the LICENSE file. + */ + +package org.jetbrains.ring + +class ChunkBuffer(var readPosition: Int, var writePosition: Int = readPosition + Random.nextInt(50)) { + private val nextRef: AtomicRef = atomic(null) + + /** + * Reference to next buffer view. Useful to chain multiple views. + * @see appendNext + * @see cleanNext + */ + var next: ChunkBuffer? get() = nextRef.value + set(newValue) { + if (newValue == null) { + cleanNext() + } else { + appendNext(newValue) + } + } + + fun cleanNext(): ChunkBuffer? { + return nextRef.getAndSet(null) + } + + private fun appendNext(chunk: ChunkBuffer) { + if (!nextRef.compareAndSet(null, chunk)) { + throw IllegalStateException("This chunk has already a next chunk.") + } + } + + inline val readRemaining: Int get() = writePosition - readPosition +} + +fun ChunkBuffer.remainingAll(): Long = remainingAll(0L) + +private tailrec fun ChunkBuffer.remainingAll(n: Long): Long { + val rem = readRemaining.toLong() + n + val next = this.next ?: return rem + return next.remainingAll(rem) +} + +class LinkedListOfBuffers(var head: ChunkBuffer = ChunkBuffer(0,0), + var remaining: Long = head.remainingAll()) { + var tailRemaining: Long = remaining - head.readRemaining + set(newValue) { + if (newValue < 0) { + error("tailRemaining is negative: $newValue") + } + val tailSize = head.next?.remainingAll() ?: 0L + if (newValue == 0L) { + if (tailSize != 0L) { + error("tailRemaining is set 0 while there is a tail of size $tailSize") + } + } + + field = newValue + } +} + +open class LinkedListWithAtomicsBenchmark { + val list: LinkedListOfBuffers + init { + val chunks: MutableList = ArrayList() + (0..BENCHMARK_SIZE/2).forEachIndexed { index, i -> + val chunk = ChunkBuffer(Random.nextInt()) + chunks.add(chunk) + if (i > 0) + chunks[i - 1].next = chunk + } + list = LinkedListOfBuffers(chunks[0]) + } + + tailrec fun ensureNext(current: ChunkBuffer = list.head): ChunkBuffer? { + val next = current.next + return when { + next == null -> null + else -> { + list.tailRemaining = Random.nextInt().toLong() + 1 + ensureNext(next) + } + } + } +} + diff --git a/performance/ring/src/main/kotlin/org/jetbrains/ring/Utils.kt b/performance/ring/src/main/kotlin/org/jetbrains/ring/Utils.kt index ce22326fb4d..837c46b290f 100644 --- a/performance/ring/src/main/kotlin/org/jetbrains/ring/Utils.kt +++ b/performance/ring/src/main/kotlin/org/jetbrains/ring/Utils.kt @@ -28,3 +28,26 @@ expect class Random() { } } +expect class AtomicRef { + /** + * Reading/writing this property maps to read/write of volatile variable. + */ + public var value: T + + /** + * Maps to [AtomicReferenceFieldUpdater.lazySet]. + */ + public fun lazySet(value: T) + + /** + * Maps to [AtomicReferenceFieldUpdater.compareAndSet]. + */ + public fun compareAndSet(expect: T, update: T): Boolean + + /** + * Maps to [AtomicReferenceFieldUpdater.getAndSet]. + */ + public fun getAndSet(value: T): T +} + +public expect fun atomic(initial: T): AtomicRef