Benchmark with atomics (#3782)
This commit is contained in:
@@ -15,6 +15,10 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
package org.jetbrains.ring
|
package org.jetbrains.ring
|
||||||
|
|
||||||
|
import java.util.concurrent.atomic.AtomicReferenceFieldUpdater
|
||||||
|
import java.util.concurrent.locks.ReentrantLock
|
||||||
|
|
||||||
//-----------------------------------------------------------------------------//
|
//-----------------------------------------------------------------------------//
|
||||||
|
|
||||||
actual class Random actual constructor() {
|
actual class Random actual constructor() {
|
||||||
@@ -32,3 +36,84 @@ actual class Random actual constructor() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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 <T> beforeUpdate(ref: AtomicRef<T>) {}
|
||||||
|
open fun <T> afterSet(ref: AtomicRef<T>, newValue: T) {}
|
||||||
|
open fun <T> afterRMW(ref: AtomicRef<T>, oldValue: T, newValue: T) {}
|
||||||
|
}
|
||||||
|
|
||||||
|
private object DefaultInterceptor : AtomicOperationInterceptor() {
|
||||||
|
override fun toString(): String = "DefaultInterceptor"
|
||||||
|
}
|
||||||
|
|
||||||
|
@Suppress("UNCHECKED_CAST")
|
||||||
|
public actual class AtomicRef<T> 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 <T> atomic(initial: T): AtomicRef<T> = AtomicRef<T>(initial)
|
||||||
@@ -16,6 +16,10 @@
|
|||||||
|
|
||||||
package org.jetbrains.ring
|
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() {
|
actual class Random actual constructor() {
|
||||||
@@ -34,3 +38,35 @@ actual class Random actual constructor() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public actual class AtomicRef<T> constructor(@PublishedApi internal val a: KAtomicRef<T>) {
|
||||||
|
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 <T> atomic(initial: T): AtomicRef<T> = AtomicRef<T>(KAtomicRef(initial))
|
||||||
@@ -205,7 +205,8 @@ class RingLauncher : Launcher() {
|
|||||||
"GraphSolver.solve" to BenchmarkEntryWithInit.create(::GraphSolverBenchmark, { solve() }),
|
"GraphSolver.solve" to BenchmarkEntryWithInit.create(::GraphSolverBenchmark, { solve() }),
|
||||||
"Casts.classCast" to BenchmarkEntryWithInit.create(::CastsBenchmark, { classCast() }),
|
"Casts.classCast" to BenchmarkEntryWithInit.create(::CastsBenchmark, { classCast() }),
|
||||||
"Casts.interfaceCast" to BenchmarkEntryWithInit.create(::CastsBenchmark, { interfaceCast() }),
|
"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() })
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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<ChunkBuffer?> = 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<ChunkBuffer> = 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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@@ -28,3 +28,26 @@ expect class Random() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
expect class AtomicRef<T> {
|
||||||
|
/**
|
||||||
|
* 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 <T> atomic(initial: T): AtomicRef<T>
|
||||||
|
|||||||
Reference in New Issue
Block a user