Benchmark with atomics (#3782)
This commit is contained in:
@@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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
|
||||
|
||||
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
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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() }),
|
||||
"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() })
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
@@ -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