Skip to content

Commit

Permalink
Optimize spin-wait loop in Spinner (#287)
Browse files Browse the repository at this point in the history
---------

Signed-off-by: Evgeniy Moiseenko <[email protected]>
Co-authored-by: Evgeniy Moiseenko <[email protected]>
  • Loading branch information
ndkoval and eupp authored Mar 20, 2024
1 parent 814f2e1 commit 5dc4e88
Show file tree
Hide file tree
Showing 2 changed files with 36 additions and 8 deletions.
42 changes: 35 additions & 7 deletions src/jvm/main/org/jetbrains/kotlinx/lincheck/util/Spinner.kt
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand All @@ -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.
*/
Expand Down Expand Up @@ -224,6 +252,6 @@ inline fun <T> 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
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
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ class LockFreeSetTest {

StressOptions()
.addCustomScenario(scenario)
.invocationsPerIteration(1000000)
.invocationsPerIteration(10_000_000)
.iterations(0)
.check(LockFreeSet::class)
}
Expand Down

0 comments on commit 5dc4e88

Please sign in to comment.