Skip to content

Commit

Permalink
Remove lock from AtomicSnapshot
Browse files Browse the repository at this point in the history
  • Loading branch information
sellmair committed Sep 25, 2024
1 parent 7249890 commit 790794a
Show file tree
Hide file tree
Showing 5 changed files with 62 additions and 19 deletions.
4 changes: 4 additions & 0 deletions evas/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,10 @@ run {
}

configurations {
register("atomicSnapshot") {
include(".*AtomicSnapshotBenchmark.*")
}

register("emit") {
include(".*EmitBenchmark.*")
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
package io.sellmair.evas.benchmark.events

Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
package io.sellmair.evas.benchmark.utils

import io.sellmair.evas.AtomicSnapshotList
import kotlinx.benchmark.*

@BenchmarkMode(Mode.AverageTime)
@OutputTimeUnit(BenchmarkTimeUnit.NANOSECONDS)
@Warmup(iterations = 10, time = 500, timeUnit = BenchmarkTimeUnit.MILLISECONDS)
@Measurement(iterations = 20, time = 1, timeUnit = BenchmarkTimeUnit.SECONDS)
@State(Scope.Benchmark)
open class AtomicSnapshotBenchmark {

private var atomicList = AtomicSnapshotList<Int>()

@Setup
fun setup() {
atomicList = AtomicSnapshotList()
}

/**
* jvmBenchmark summary:
* Benchmark Mode Cnt Score Error Units
* AtomicSnapshotBenchmark.writeReadWrite avgt 20 23.781 ± 0.127 ns/op
*
* macosArm64Benchmark summary:
* Benchmark Mode Cnt Score Error Units
* CreateFlowBenchmark.appendList avgt 20 158.378 ± 0.612 ns/op
*/
@Benchmark
fun writeReadWrite(): Int {
atomicList.write { it.add(42) }
try {
return atomicList.snapshot().size
} finally {
atomicList.write { it.clear() }
}
}
}
34 changes: 17 additions & 17 deletions evas/src/commonMain/kotlin/io/sellmair/evas/AtomicSnapshot.kt
Original file line number Diff line number Diff line change
@@ -1,21 +1,18 @@

@file:Suppress("FunctionName")

package io.sellmair.evas

import kotlinx.atomicfu.atomic
import kotlinx.atomicfu.locks.reentrantLock
import kotlinx.atomicfu.locks.withLock
import kotlinx.atomicfu.loop
import kotlin.native.concurrent.ThreadLocal

import kotlin.concurrent.Volatile

internal fun <T> AtomicSnapshotList(initial: MutableList<T> = ArrayList()): AtomicSnapshot<MutableList<T>, List<T>> =
internal fun <T> AtomicSnapshotList(initial: MutableList<T> = mutableListOf()): AtomicSnapshot<MutableList<T>, List<T>> =
AtomicSnapshot(initial, { it.toList() })

internal fun <K, V> AtomicSnapshotMap(initial: MutableMap<K, V> = mutableMapOf()): AtomicSnapshot<MutableMap<K, V>, Map<K, V>> =
AtomicSnapshot(initial, { it.toMap() })


/**
* Utility class for a lock-free read/write mechanism.
* Reads will be performed by using an immutable [snapshot] of the current value (waiting for writes to have finished)
Expand All @@ -26,24 +23,27 @@ internal class AtomicSnapshot<T, S>(
private val createSnapshot: (T) -> S
) {
private val writing = atomic(false)
private val writeLock = reentrantLock()
private val snapshot = atomic(createSnapshot(value))

@Volatile
private var snapshot = createSnapshot(value)

fun write(body: (T) -> Unit) {
writeLock.withLock {
writing.value = true
try {
body(value)
} finally {
snapshot.value = createSnapshot(value)
writing.value = false
}
while (true) {
if (writing.compareAndSet(expect = false, update = true)) break
}

try {
body(value)
} finally {
snapshot = createSnapshot(value)
writing.value = false
}
}

fun snapshot(): S {
writing.loop { writing ->
if (!writing) return snapshot.value
if (!writing) return snapshot
}
}
}

3 changes: 1 addition & 2 deletions evas/src/commonMain/kotlin/io/sellmair/evas/Events.kt
Original file line number Diff line number Diff line change
Expand Up @@ -312,8 +312,7 @@ Implementation!
internal class EventsImpl : Events {
private val unconfinedScope = CoroutineScope(Dispatchers.Unconfined)

internal val typedChannels =
AtomicSnapshotMap<KClass<*>, AtomicSnapshot<MutableList<Channel<Dispatch<*>>>, List<Channel<Dispatch<*>>>>>()
internal val typedChannels = AtomicSnapshotMap<KClass<*>, AtomicSnapshot<MutableList<Channel<Dispatch<*>>>, List<Channel<Dispatch<*>>>>>()

internal class Dispatch<out T>(val event: T, val job: CompletableJob?)

Expand Down

0 comments on commit 790794a

Please sign in to comment.