Skip to content

Instantly share code, notes, and snippets.

@plinyar
Last active December 2, 2023 11:28
Show Gist options
  • Save plinyar/1961d1fcefea5a8d31b6ae1938d26f68 to your computer and use it in GitHub Desktop.
Save plinyar/1961d1fcefea5a8d31b6ae1938d26f68 to your computer and use it in GitHub Desktop.
JMH tests on cost of using ThreadLocal and AtomicReference
package net.lcharge
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import org.openjdk.jmh.annotations.*
import org.openjdk.jmh.infra.Blackhole
import java.util.concurrent.atomic.AtomicReference
/**
* Results:
* -----------------------
* Benchmark Mode Cnt Score Error Units
* ActiveValueBenchmark.atomicValues thrpt 10 25358314.779 ± 3056160.402 ops/s
* ActiveValueBenchmark.simpleValues thrpt 10 52953283.100 ± 1934329.855 ops/s
* ActiveValueBenchmark.trackableValues thrpt 10 15685814.251 ± 1232896.698 ops/s
* ActiveValueBenchmark.unsafeTrackableValues thrpt 10 15434336.683 ± 834336.305 ops/s
* stateFlowValues are by 2 mln slower than atomicValues
*
*/
@BenchmarkMode(Mode.Throughput)
@Warmup(iterations = 1, batchSize = 10, time = 2)
@Measurement(iterations = 2, batchSize = 10, time = 2)
@Threads(4)
//@Fork(2)
open class ActiveValueBenchmark {
@State(Scope.Thread)
open class Data() {
var simpleValues = valueTemplates.map { SimpleValue(it) }
var atomicValues = valueTemplates.map { AtomicValue(it) }
var trackableValues = valueTemplates.map { TrackableValue(it) }
var stateFlowValues = valueTemplates.map { StateFlowValue(it) }
var unsafeTrackableValue = valueTemplates.map { UnsafeTrackableValue(it) }
@Setup(Level.Trial)
fun setUp() {
// just to be sure that Java is not so smart as to exclude condition inside [track] method
// during compilation
TrackableValue.threadLocal.set(false)
UnsafeTrackableValue.threadLocal.set(false)
simpleValues.forEach { it.value += 0.00001 }
}
}
@Benchmark
fun simpleValues(state: Data, blackhole: Blackhole) {
blackhole.consume(calculation(state.simpleValues))
}
@Benchmark
fun atomicValues(state: Data, blackhole: Blackhole) {
blackhole.consume(calculation(state.atomicValues))
}
@Benchmark
fun trackableValues(state: Data, blackhole: Blackhole) {
blackhole.consume(calculation(state.trackableValues))
}
@Benchmark
fun unsafeTrackableValues(state: Data, blackhole: Blackhole) {
blackhole.consume(calculation(state.unsafeTrackableValue))
}
// @Benchmark
// fun stateFlowValues(state: Data, blackhole: Blackhole) {
// blackhole.consume(calculation(state.stateFlowValues))
// }
fun calculation(v : List<ValueGet<Double>>) =
if (v[0]() > 0.0)
if (v[1]() < v[2]())
(v[3]() + v[4]()) * v[5]() - v[6]()
else
0.0
else
0.0
}
val valueTemplates = listOf(1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0)
typealias ValueGet<T> = () -> T
class SimpleValue<T>(var value:T) : ValueGet<T> {
override fun invoke(): T = value
}
class AtomicValue<T>(value:T) : ValueGet<T> {
private val valueBox = AtomicReference<T>(value)
override fun invoke(): T = valueBox.get()
}
class StateFlowValue<T>(value:T) : ValueGet<T> {
private val valueBox = MutableStateFlow(value)
override fun invoke(): T = valueBox.value
}
class UnsafeTrackableValue<T>(val value:T) : ValueGet<T> {
override fun invoke(): T = value.also { track(this) }
companion object {
val threadLocal = ThreadLocal<Boolean?>()
fun track(who: UnsafeTrackableValue<*>) {
if (threadLocal.get() == true)
throw Exception("Should not go here $who")
}
}
}
class TrackableValue<T>(value:T) : ValueGet<T> {
private val valueBox = AtomicReference<T>(value)
override fun invoke(): T = valueBox.get().also { track(this) }
companion object {
val threadLocal = ThreadLocal<Boolean?>()
fun track(who: TrackableValue<*>) {
if (threadLocal.get() == true)
throw Exception("Should not go here $who")
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment