From 5dc4e88bfea47e5f5d54fd10d17ee2e19ed1f4d7 Mon Sep 17 00:00:00 2001 From: Nikita Koval Date: Wed, 20 Mar 2024 01:18:29 +0100 Subject: [PATCH] Optimize spin-wait loop in Spinner (#287) --------- Signed-off-by: Evgeniy Moiseenko Co-authored-by: Evgeniy Moiseenko --- .../kotlinx/lincheck/util/Spinner.kt | 42 +++++++++++++++---- .../linearizability/LockFreeSetTest.kt | 2 +- 2 files changed, 36 insertions(+), 8 deletions(-) diff --git a/src/jvm/main/org/jetbrains/kotlinx/lincheck/util/Spinner.kt b/src/jvm/main/org/jetbrains/kotlinx/lincheck/util/Spinner.kt index a5d7d3f39..6dc585015 100644 --- a/src/jvm/main/org/jetbrains/kotlinx/lincheck/util/Spinner.kt +++ b/src/jvm/main/org/jetbrains/kotlinx/lincheck/util/Spinner.kt @@ -130,10 +130,11 @@ class Spinner(val nThreads: Int = -1) { * for example, to fall back into a blocking synchronization. */ fun spin(): Boolean { - // spin a few iterations - repeat(spinLoopIterationsPerCall) { - counter++ - } + // perform spin waiting + Thread.onSpinWait() + spinWait() + // update the counter + counter += spinLoopIterationsPerCall // if yield limit is approached, // then yield and give other threads the opportunity to run if (counter % yieldLimit == 0) { @@ -148,6 +149,33 @@ class Spinner(val nThreads: Int = -1) { return true } + /** + * Auxiliary variable storing a pseudo-random value, + * which is used inside the [spinWait] to perform spin waiting. + */ + private var sink: Long = System.nanoTime() + + /** + * Implements a spin waiting procedure. + */ + private fun spinWait() { + // Initialize with a pseudo-random number to prevent optimizations. + var x = sink + // We want to perform few spins while avoiding accesses to the shared memory. + // To achieve this, we do some arithmetic operations on a local variable + // and try to obfuscate the loop body so that the compiler + // would not be able to optimize it out. + for (i in spinLoopIterationsPerCall downTo 1) { + x += (31 * x + 0xBEEF + i) and (0xFFFFFFFFFFFFFFFL) + } + // This if statement ensures that the result of the computation + // will have a visible side effect and thus will not be optimized, + // but at the same time it avoids the actual store on a hot-path. + if (x == 0xDEADL) { + sink += x + } + } + /** * Resets the state of the spinner. */ @@ -224,6 +252,6 @@ inline fun Spinner.spinWaitBoundedFor(getter: () -> T?): T? { return null } -private const val SPIN_LOOP_ITERATIONS_PER_CALL : Int = 1 shl 5 // 32 -private const val SPIN_LOOP_ITERATIONS_BEFORE_YIELD : Int = 1 shl 14 // 16,384 -private const val SPIN_LOOP_ITERATIONS_BEFORE_EXIT : Int = 1 shl 20 // 1,048,576 \ No newline at end of file +private const val SPIN_LOOP_ITERATIONS_PER_CALL : Int = 100 +private const val SPIN_LOOP_ITERATIONS_BEFORE_YIELD : Int = 10_000_000 +private const val SPIN_LOOP_ITERATIONS_BEFORE_EXIT : Int = 100_000_000 \ No newline at end of file diff --git a/src/jvm/test/org/jetbrains/kotlinx/lincheck_test/verifier/linearizability/LockFreeSetTest.kt b/src/jvm/test/org/jetbrains/kotlinx/lincheck_test/verifier/linearizability/LockFreeSetTest.kt index cc1ca9b14..21a773c6a 100644 --- a/src/jvm/test/org/jetbrains/kotlinx/lincheck_test/verifier/linearizability/LockFreeSetTest.kt +++ b/src/jvm/test/org/jetbrains/kotlinx/lincheck_test/verifier/linearizability/LockFreeSetTest.kt @@ -38,7 +38,7 @@ class LockFreeSetTest { StressOptions() .addCustomScenario(scenario) - .invocationsPerIteration(1000000) + .invocationsPerIteration(10_000_000) .iterations(0) .check(LockFreeSet::class) }