Benchmark with atomics (#3782)

This commit is contained in:
LepilkinaElena
2020-01-24 17:11:16 +03:00
committed by GitHub
parent 68db5733a8
commit d1a7ea2ed3
5 changed files with 236 additions and 3 deletions
@@ -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))
+2 -1
View File
@@ -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>