1 package benchmarks.debug
2 
3 import kotlinx.coroutines.*
4 import kotlinx.coroutines.debug.*
5 import org.openjdk.jmh.annotations.*
6 import org.openjdk.jmh.annotations.State
7 import java.util.concurrent.*
8 import java.util.concurrent.atomic.AtomicInteger
9 
10 /**
11  * The benchmark is supposed to show the DebugProbes overhead for a non-concurrent sequence builder.
12  * The code is actually part of the IDEA codebase, originally reported here: https://github.com/Kotlin/kotlinx.coroutines/issues/3527
13  */
14 @Warmup(iterations = 5, time = 1)
15 @Measurement(iterations = 5, time = 1)
16 @Fork(value = 1)
17 @BenchmarkMode(Mode.AverageTime)
18 @OutputTimeUnit(TimeUnit.MICROSECONDS)
19 @State(Scope.Benchmark)
20 open class DebugSequenceOverheadBenchmark {
21 
generateRecursiveSequencenull22     private fun <Node> generateRecursiveSequence(
23         initialSequence: Sequence<Node>,
24         children: (Node) -> Sequence<Node>
25     ): Sequence<Node> {
26         return sequence {
27             val initialIterator = initialSequence.iterator()
28             if (!initialIterator.hasNext()) {
29                 return@sequence
30             }
31             val visited = HashSet<Node>()
32             val sequences = ArrayDeque<Sequence<Node>>()
33             sequences.addLast(initialIterator.asSequence())
34             while (sequences.isNotEmpty()) {
35                 val currentSequence = sequences.removeFirst()
36                 for (node in currentSequence) {
37                     if (visited.add(node)) {
38                         yield(node)
39                         sequences.addLast(children(node))
40                     }
41                 }
42             }
43         }
44     }
45 
46     @Param("true", "false")
47     var withDebugger = false
48 
49     @Setup
setupnull50     fun setup() {
51         DebugProbes.sanitizeStackTraces = false
52         DebugProbes.enableCreationStackTraces = false
53         if (withDebugger) {
54             DebugProbes.install()
55         }
56     }
57 
58     @TearDown
tearDownnull59     fun tearDown() {
60         if (withDebugger) {
61             DebugProbes.uninstall()
62         }
63     }
64 
65     // Shows the overhead of sequence builder with debugger enabled
66     @Benchmark
<lambda>null67     fun runSequenceSingleThread(): Int = runBlocking {
68         generateRecursiveSequence((1..100).asSequence()) {
69             (1..it).asSequence()
70         }.sum()
71     }
72 
73     // Shows the overhead of sequence builder with debugger enabled and debugger is concurrently stressed out
74     @Benchmark
<lambda>null75     fun runSequenceMultipleThreads(): Int = runBlocking {
76         val result = AtomicInteger(0)
77         repeat(Runtime.getRuntime().availableProcessors()) {
78             launch(Dispatchers.Default) {
79                 result.addAndGet(generateRecursiveSequence((1..100).asSequence()) {
80                     (1..it).asSequence()
81                 }.sum())
82             }
83         }
84         result.get()
85     }
86 }
87