Skip to content

Commit

Permalink
Add template for benchmarks
Browse files Browse the repository at this point in the history
  • Loading branch information
aartdem committed May 5, 2024
1 parent b632cee commit ed1d127
Show file tree
Hide file tree
Showing 6 changed files with 172 additions and 0 deletions.
Empty file added work1/README.md
Empty file.
5 changes: 5 additions & 0 deletions work1/build.gradle.kts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
plugins {
kotlin("jvm") version "1.9.22"
application
}

group = "stack"
Expand All @@ -23,4 +24,8 @@ tasks.test {

kotlin {
jvmToolchain(17)
}

application {
mainClass = "stack.MainKt"
}
50 changes: 50 additions & 0 deletions work1/src/main/kotlin/stack/Main.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
package stack

import de.m3y.kformat.Table
import de.m3y.kformat.table
import org.apache.commons.math3.geometry.euclidean.oned.Interval
import stack.benchmark.MeasureScenario
import stack.benchmark.Operation
import stack.benchmark.StackBenchmark
import stack.simple.ConcurrentTreiberStack

fun main() {
// prepare cases
// val threadNums = listOf(1, 2, 4, 6)
// val operations = buildList {
// for (i in 1e7.toInt()..1e8.toInt() step 1e7.toInt()) {
// add(i)
// }
// }
val threadNums = listOf(6)
val operations = listOf(100)
// calculating
val stackBenchmark = StackBenchmark<Int>()
val results = mutableListOf(mutableListOf<Interval>())
for (threadNum in threadNums) {
val currentResult = mutableListOf<Interval>()
for (opCount in operations) {
val singleThreadRandomMeasureScenario = MeasureScenario(
threadNum, opCount / threadNum,
ConcurrentTreiberStack(),
Operation.Push(0),
MeasureScenario.randomScenarioIntGenerator
)
stackBenchmark.loadScenario(singleThreadRandomMeasureScenario)
val workTime = stackBenchmark.startAndMeasure(20) ?: throw IllegalStateException()
currentResult.add(workTime)
println("Threads: $threadNum | Operations: $opCount | DONE")
}
results.add(currentResult)
}
table {
header(listOf("") + operations.map { it.toString() })

// TODO("add my rows")
row(10, "b...1", 2.1f, "foo")

hints {
borderStyle = Table.BorderStyle.SINGLE_LINE // or NONE
}
}.print()
}
30 changes: 30 additions & 0 deletions work1/src/main/kotlin/stack/benchmark/MeasureScenario.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
package stack.benchmark

import stack.common.ConcurrentStack
import kotlin.random.Random

class MeasureScenario<T>(
val threadsNum: Int,
val operationsPerThread: Int,
val initialStack: ConcurrentStack<T>,
val firstOperation: Operation<T>,
val operationGenerator: (Operation<T>) -> Operation<T>
) {
companion object {
val randomScenarioIntGenerator: (Operation<Int>) -> Operation<Int> = { _ ->
when (Random.nextInt(3)) {
0 -> Operation.Push(Random.nextInt())
1 -> Operation.Pop()
2 -> Operation.Top()
else -> throw IllegalStateException()
}
}
val pushPopIntGenerator: (Operation<Int>) -> Operation<Int> = { last ->
when (last) {
is Operation.Push -> Operation.Pop()
is Operation.Pop -> Operation.Push(Random.nextInt())
is Operation.Top -> Operation.Push(Random.nextInt())
}
}
}
}
15 changes: 15 additions & 0 deletions work1/src/main/kotlin/stack/benchmark/Operation.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
package stack.benchmark

import stack.common.ConcurrentStack

sealed interface Operation<T> {
class Push<T>(val value: T) : Operation<T>
class Pop<T> : Operation<T>
class Top<T> : Operation<T>

fun toStackOperation(): ConcurrentStack<T>.() -> Unit = when (this) {
is Push<T> -> fun ConcurrentStack<T>.() = (::push)(value)
is Pop<T> -> fun ConcurrentStack<T>.() { (::pop)() }
is Top<T> -> fun ConcurrentStack<T>.() { (::top)() }
}
}
72 changes: 72 additions & 0 deletions work1/src/main/kotlin/stack/benchmark/StackBenchmark.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
package stack.benchmark

import org.apache.commons.math3.distribution.TDistribution
import org.apache.commons.math3.geometry.euclidean.oned.Interval
import org.apache.commons.math3.stat.descriptive.SummaryStatistics
import kotlin.concurrent.thread
import kotlin.math.sqrt
import kotlin.time.Duration.Companion.milliseconds
import kotlin.time.DurationUnit


class StackBenchmark<T> {
private val threads: MutableList<Thread> = mutableListOf()

private fun clearScenario() {
threads.clear()
}

/**
* Load the scenario. If scenario was already loaded, overrides it
*/
fun loadScenario(measureScenario: MeasureScenario<T>) {
clearScenario()

val operations = buildList(measureScenario.operationsPerThread) {
add(measureScenario.firstOperation)
repeat(measureScenario.operationsPerThread - 1) {
add(measureScenario.operationGenerator(this.last()))
}
}.map { it.toStackOperation() }

repeat(measureScenario.threadsNum) {
threads.add(thread(start = false) {
operations.forEach { it.invoke(measureScenario.initialStack) }
})
}
}

/**
* Execute loaded scenario [n] times and returns work time in milliseconds. Returns null if scenario was not loaded
*/
fun startAndMeasure(n: Int): Interval? {
if (threads.isEmpty()) return null

val results = buildList(n) {
val initialTime = System.currentTimeMillis()
threads.forEach { it.start() }
threads.forEach { it.join() }
val workTimeMills = (System.currentTimeMillis() - initialTime)
add(workTimeMills.milliseconds.toDouble(DurationUnit.SECONDS))
}

return calculateConfidenceInterval(results)
}

/** Implemented using [source](https://gist.github.com/gcardone/5536578) */
private fun calculateConfidenceInterval(results: List<Double>): Interval {
val stats = SummaryStatistics().apply {
results.forEach { this.addValue(it) }
}
val tDist = TDistribution(stats.n.toDouble())
val crVal = tDist.inverseCumulativeProbability(0.975)
val ci = crVal * stats.standardDeviation / sqrt(stats.n.toDouble())
return Interval(stats.mean - ci, stats.mean + ci)
}

fun printLoadedScenario() {
threads.forEach {
println(it.toString())
}
}
}

0 comments on commit ed1d127

Please sign in to comment.