Created
          September 19, 2025 14:13 
        
      - 
      
- 
        Save lbialy/9f3732ce9901ba76ee2da9dd86f7df33 to your computer and use it in GitHub Desktop. 
    Different thread yielding techniques with Loom 
  
        
  
    
      This file contains hidden or 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
    
  
  
    
  | //> using scala 3.7.3 | |
| //> using dep com.softwaremill.ox::core:1.0.0 | |
| import scala.concurrent.duration.* | |
| import java.time.Instant | |
| import ox.* | |
| import scala.util.boundary | |
| import java.util.ArrayList | |
| import java.util.concurrent.locks.LockSupport | |
| object TestYielding: | |
| inline def nowMillis = System.currentTimeMillis() | |
| inline def busyLoop(howOftenYield: Duration, duration: Duration, inline yieldOp: => Unit): Unit = | |
| val start = Instant.now() | |
| val endMillis = start.plusMillis(duration.toMillis).toEpochMilli() | |
| val howOftenYieldMillis = howOftenYield.toMillis | |
| var lastYield = start.toEpochMilli() | |
| boundary: | |
| while true do | |
| val now = nowMillis | |
| if now - lastYield >= howOftenYieldMillis then | |
| lastYield = now | |
| yieldOp | |
| if now >= endMillis then boundary.break() | |
| inline def test( | |
| howOftenYield: Duration, | |
| howOftenProbeLiveness: Duration, | |
| duration: Duration, | |
| cores: Int, | |
| verbose: Boolean, | |
| inline yieldOp: => Unit | |
| ): Unit = | |
| val maxProbes = duration / howOftenProbeLiveness - 1 | |
| supervised: | |
| // put busy loops in separate VTs | |
| forkUser: | |
| (1 to cores).mapPar(cores): idx => | |
| if verbose then println(s"Starting busy loop $idx") | |
| busyLoop(howOftenYield, duration, yieldOp) | |
| if verbose then println(s"Finished busy loop $idx") | |
| // run a single separate VT that probes liveness | |
| val livenessFork = forkUser: | |
| val howOftenProbeLivenessMillis = howOftenProbeLiveness.toMillis | |
| val startMillis = nowMillis | |
| val endMillis = startMillis + duration.toMillis | |
| val sampleTimestamps = ArrayList[Long]() | |
| var lastProbe = startMillis | |
| boundary: | |
| while true do | |
| val now = nowMillis | |
| if now >= endMillis then boundary.break() | |
| if now - lastProbe >= howOftenProbeLivenessMillis then | |
| if verbose then | |
| println( | |
| s"Probed liveness: now ${now}, last probe ${lastProbe}, difference ${now - lastProbe}, how often probe liveness ${howOftenProbeLivenessMillis}" | |
| ) | |
| lastProbe = now | |
| sampleTimestamps.add(now) | |
| Thread.sleep(howOftenProbeLivenessMillis) | |
| sampleTimestamps | |
| val livenessRecord = livenessFork.join() | |
| println(s"Max liveness probes possible: ${maxProbes}") | |
| val capturedProbesPercentage = livenessRecord.size() * 100.0 / maxProbes | |
| val capturedProbesPercentageString = capturedProbesPercentage.toInt.toString + "%" | |
| println(s"Captured ${livenessRecord.size()} liveness probes (${capturedProbesPercentageString})") | |
| @main def main(): Unit = | |
| val cores = Runtime.getRuntime().availableProcessors() | |
| println(s"Testing with ${cores} cores") | |
| println("Testing always yielding, 1 core in busy loop, using Thread.yield, should capture 100% of liveness probes.") | |
| TestYielding.test( | |
| howOftenYield = 10.millis, | |
| howOftenProbeLiveness = 100.millis, | |
| duration = 5.seconds, | |
| cores = 1, | |
| verbose = false, | |
| yieldOp = Thread.`yield` | |
| ) | |
| println("--------------------------------") | |
| println("Testing never yielding, all cores in busy loop, using Thread.yield, should capture 2% of liveness probes.") | |
| TestYielding.test( | |
| howOftenYield = 1.hour, | |
| howOftenProbeLiveness = 100.millis, | |
| duration = 5.seconds, | |
| cores = cores, | |
| verbose = false, | |
| yieldOp = Thread.`yield` | |
| ) | |
| println("--------------------------------") | |
| println("Testing yielding every 10ms, all cores in busy loop, using Thread.yield, should capture 100% of liveness probes.") | |
| TestYielding.test( | |
| howOftenYield = 10.millis, | |
| howOftenProbeLiveness = 100.millis, | |
| duration = 5.seconds, | |
| cores = cores, | |
| verbose = false, | |
| yieldOp = Thread.`yield` | |
| ) | |
| println("--------------------------------") | |
| println("Testing yielding every 10ms, all cores in busy loop, using Thread.sleep(0), should capture 100% of liveness probes.") | |
| TestYielding.test( | |
| howOftenYield = 10.millis, | |
| howOftenProbeLiveness = 100.millis, | |
| duration = 5.seconds, | |
| cores = cores, | |
| verbose = false, | |
| yieldOp = Thread.sleep(0) | |
| ) | |
| println("--------------------------------") | |
| println("Testing yielding every 10ms, all cores in busy loop, using LockSupport.parkNanos(0), should capture 100% of liveness probes.") | |
| TestYielding.test( | |
| howOftenYield = 10.millis, | |
| howOftenProbeLiveness = 100.millis, | |
| duration = 5.seconds, | |
| cores = cores, | |
| verbose = false, | |
| yieldOp = LockSupport.parkNanos(0) | |
| ) | |
| println("--------------------------------") | |
| println("Testing yielding every 10ms, all cores in busy loop, using LockSupport.parkNanos(1), should capture 100% of liveness probes.") | |
| TestYielding.test( | |
| howOftenYield = 10.millis, | |
| howOftenProbeLiveness = 100.millis, | |
| duration = 5.seconds, | |
| cores = cores, | |
| verbose = false, | |
| yieldOp = LockSupport.parkNanos(1) | |
| ) | |
| println("--------------------------------") | 
  
    Sign up for free
    to join this conversation on GitHub.
    Already have an account?
    Sign in to comment