Last active
December 2, 2023 11:28
-
-
Save plinyar/1961d1fcefea5a8d31b6ae1938d26f68 to your computer and use it in GitHub Desktop.
JMH tests on cost of using ThreadLocal and AtomicReference
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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