JVM multithreaded scope pool now turned on by default
This commit is contained in:
1 parent
38c1b3c209
commit
2af5852d44
16 files changed
+490
-35
No files matched your search
@@ -206,6 +206,10 @@ suspend fun DocTest.test(_scope: Scope? = null) {
|
||||
expectedResult != result
|
||||
) {
|
||||
System.err.println("\nfailed: ${this.detailedString}")
|
||||
System.err.println("[DEBUG_LOG] expectedOutput=\n${expectedOutput}")
|
||||
System.err.println("[DEBUG_LOG] actualOutput=\n${collectedOutput}")
|
||||
System.err.println("[DEBUG_LOG] expectedResult=${expectedResult}")
|
||||
System.err.println("[DEBUG_LOG] actualResult=${result}")
|
||||
}
|
||||
error?.let {
|
||||
fail(it.message, it)
|
||||
|
||||
@@ -0,0 +1,66 @@
|
||||
/*
|
||||
* Multithreaded benchmark to quantify SCOPE_POOL speedup on JVM.
|
||||
*/
|
||||
|
||||
import kotlinx.coroutines.*
|
||||
import net.sergeych.lyng.PerfFlags
|
||||
import net.sergeych.lyng.Scope
|
||||
import net.sergeych.lyng.obj.ObjInt
|
||||
import kotlin.math.max
|
||||
import kotlin.math.min
|
||||
import kotlin.test.Test
|
||||
import kotlin.test.assertEquals
|
||||
|
||||
class ConcurrencyCallBenchmarkTest {
|
||||
|
||||
private suspend fun parallelEval(workers: Int, script: String): List<Long> = coroutineScope {
|
||||
(0 until workers).map { async { (Scope().eval(script) as ObjInt).value } }.awaitAll()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun benchmark_multithread_calls_off_on() = runBlocking {
|
||||
val cpu = Runtime.getRuntime().availableProcessors()
|
||||
val workers = min(max(2, cpu), 8)
|
||||
val iterations = 15_000 // per worker; keep CI fast
|
||||
val script = """
|
||||
fun f0() { 1 }
|
||||
fun f1(a) { a }
|
||||
fun f2(a,b) { a + b }
|
||||
fun f3(a,b,c) { a + b + c }
|
||||
fun f4(a,b,c,d) { a + b + c + d }
|
||||
var s = 0
|
||||
var i = 0
|
||||
while (i < $iterations) {
|
||||
s = s + f0()
|
||||
s = s + f1(1)
|
||||
s = s + f2(1, 1)
|
||||
s = s + f3(1, 1, 1)
|
||||
s = s + f4(1, 1, 1, 1)
|
||||
i = i + 1
|
||||
}
|
||||
s
|
||||
""".trimIndent()
|
||||
val expected = (1 + 1 + 2 + 3 + 4).toLong() * iterations
|
||||
|
||||
// OFF
|
||||
PerfFlags.SCOPE_POOL = false
|
||||
val t0 = System.nanoTime()
|
||||
val off = withContext(Dispatchers.Default) { parallelEval(workers, script) }
|
||||
val t1 = System.nanoTime()
|
||||
// ON
|
||||
PerfFlags.SCOPE_POOL = true
|
||||
val t2 = System.nanoTime()
|
||||
val on = withContext(Dispatchers.Default) { parallelEval(workers, script) }
|
||||
val t3 = System.nanoTime()
|
||||
// reset
|
||||
PerfFlags.SCOPE_POOL = false
|
||||
|
||||
off.forEach { assertEquals(expected, it) }
|
||||
on.forEach { assertEquals(expected, it) }
|
||||
|
||||
val offMs = (t1 - t0) / 1_000_000.0
|
||||
val onMs = (t3 - t2) / 1_000_000.0
|
||||
val speedup = offMs / onMs
|
||||
println("[DEBUG_LOG] [BENCH] ConcurrencyCallBenchmark workers=$workers iters=$iterations each: OFF=${"%.3f".format(offMs)} ms, ON=${"%.3f".format(onMs)} ms, speedup=${"%.2f".format(speedup)}x")
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,97 @@
|
||||
/*
|
||||
* Multithreaded stress tests for ScopePool on JVM.
|
||||
*/
|
||||
|
||||
import kotlinx.coroutines.*
|
||||
import net.sergeych.lyng.PerfFlags
|
||||
import net.sergeych.lyng.Scope
|
||||
import net.sergeych.lyng.obj.ObjInt
|
||||
import kotlin.math.max
|
||||
import kotlin.math.min
|
||||
import kotlin.test.Test
|
||||
import kotlin.test.assertEquals
|
||||
|
||||
class MultiThreadPoolingStressJvmTest {
|
||||
|
||||
private suspend fun parallelEval(workers: Int, block: suspend (Int) -> Long): List<Long> = coroutineScope {
|
||||
(0 until workers).map { w -> async { block(w) } }.awaitAll()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun parallel_shallow_calls_correct_off_on() = runBlocking {
|
||||
val cpu = Runtime.getRuntime().availableProcessors()
|
||||
val workers = min(max(2, cpu), 8)
|
||||
val iterations = 25_000 // keep CI reasonable
|
||||
val script = """
|
||||
fun f0(a){ a }
|
||||
fun f1(a,b){ a + b }
|
||||
fun f2(a,b,c){ a + b + c }
|
||||
var s = 0
|
||||
var i = 0
|
||||
while(i < $iterations){
|
||||
s = s + f0(1)
|
||||
s = s + f1(1,1)
|
||||
s = s + f2(1,1,1)
|
||||
i = i + 1
|
||||
}
|
||||
s
|
||||
""".trimIndent()
|
||||
|
||||
fun expected() = (1 + 2 + 3).toLong() * iterations
|
||||
|
||||
// OFF
|
||||
PerfFlags.SCOPE_POOL = false
|
||||
val offResults = withContext(Dispatchers.Default) {
|
||||
parallelEval(workers) {
|
||||
val r = (Scope().eval(script) as ObjInt).value
|
||||
r
|
||||
}
|
||||
}
|
||||
// ON
|
||||
PerfFlags.SCOPE_POOL = true
|
||||
val onResults = withContext(Dispatchers.Default) {
|
||||
parallelEval(workers) {
|
||||
val r = (Scope().eval(script) as ObjInt).value
|
||||
r
|
||||
}
|
||||
}
|
||||
// reset
|
||||
PerfFlags.SCOPE_POOL = false
|
||||
|
||||
val exp = expected()
|
||||
offResults.forEach { assertEquals(exp, it) }
|
||||
onResults.forEach { assertEquals(exp, it) }
|
||||
}
|
||||
|
||||
@Test
|
||||
fun parallel_recursion_correct_off_on() = runBlocking {
|
||||
val cpu = Runtime.getRuntime().availableProcessors()
|
||||
val workers = min(max(2, cpu), 8)
|
||||
val depth = 12
|
||||
val script = """
|
||||
fun fact(x){ if(x <= 1) 1 else x * fact(x-1) }
|
||||
fact($depth)
|
||||
""".trimIndent()
|
||||
val expected = (1..depth).fold(1L){a,b->a*b}
|
||||
|
||||
// OFF
|
||||
PerfFlags.SCOPE_POOL = false
|
||||
val offResults = withContext(Dispatchers.Default) {
|
||||
parallelEval(workers) {
|
||||
(Scope().eval(script) as ObjInt).value
|
||||
}
|
||||
}
|
||||
// ON
|
||||
PerfFlags.SCOPE_POOL = true
|
||||
val onResults = withContext(Dispatchers.Default) {
|
||||
parallelEval(workers) {
|
||||
(Scope().eval(script) as ObjInt).value
|
||||
}
|
||||
}
|
||||
// reset
|
||||
PerfFlags.SCOPE_POOL = false
|
||||
|
||||
offResults.forEach { assertEquals(expected, it) }
|
||||
onResults.forEach { assertEquals(expected, it) }
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user