Merge pull request #360 from mipt-npm/commandertvis/just-calculate

Add justCalculate benchmark to inspect overhead of Expression API, some minor refactorings
This commit is contained in:
Alexander Nozik 2021-06-11 09:53:05 +03:00 committed by GitHub
commit ecab9a458f
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
12 changed files with 110 additions and 40 deletions

View File

@ -14,22 +14,51 @@ import space.kscience.kmath.expressions.*
import space.kscience.kmath.operations.DoubleField
import space.kscience.kmath.operations.bindSymbol
import space.kscience.kmath.operations.invoke
import kotlin.math.sin
import kotlin.random.Random
@State(Scope.Benchmark)
internal class ExpressionsInterpretersBenchmark {
/**
* Benchmark case for [Expression] created with [expressionInExtendedField].
*/
@Benchmark
fun functionalExpression(blackhole: Blackhole) = invokeAndSum(functional, blackhole)
/**
* Benchmark case for [Expression] created with [toExpression].
*/
@Benchmark
fun mstExpression(blackhole: Blackhole) = invokeAndSum(mst, blackhole)
/**
* Benchmark case for [Expression] created with [compileToExpression].
*/
@Benchmark
fun asmExpression(blackhole: Blackhole) = invokeAndSum(asm, blackhole)
/**
* Benchmark case for [Expression] implemented manually with `kotlin.math` functions.
*/
@Benchmark
fun rawExpression(blackhole: Blackhole) = invokeAndSum(raw, blackhole)
/**
* Benchmark case for direct computation w/o [Expression].
*/
@Benchmark
fun justCalculate(blackhole: Blackhole) {
val random = Random(0)
var sum = 0.0
repeat(times) {
val x = random.nextDouble()
sum += x * 2.0 + 2.0 / x - 16.0 / sin(x)
}
blackhole.consume(sum)
}
private fun invokeAndSum(expr: Expression<Double>, blackhole: Blackhole) {
val random = Random(0)
var sum = 0.0
@ -42,23 +71,24 @@ internal class ExpressionsInterpretersBenchmark {
}
private companion object {
private val x: Symbol by symbol
private val algebra: DoubleField = DoubleField
private val x by symbol
private val algebra = DoubleField
private const val times = 1_000_000
private val functional: Expression<Double> = DoubleField.expressionInExtendedField {
private val functional = DoubleField.expressionInExtendedField {
bindSymbol(x) * number(2.0) + number(2.0) / bindSymbol(x) - number(16.0) / sin(bindSymbol(x))
}
private val node = MstExtendedField {
bindSymbol(x) * 2.0 + number(2.0) / bindSymbol(x) - number(16.0) / sin(bindSymbol(x))
x * 2.0 + number(2.0) / x - number(16.0) / sin(x)
}
private val mst: Expression<Double> = node.toExpression(DoubleField)
private val asm: Expression<Double> = node.compileToExpression(DoubleField)
private val mst = node.toExpression(DoubleField)
private val asm = node.compileToExpression(DoubleField)
private val raw: Expression<Double> = Expression { args ->
args.getValue(x) * 2.0 + 2.0 / args.getValue(x) - 16.0 / kotlin.math.sin(args.getValue(x))
private val raw = Expression<Double> { args ->
val x = args[x]!!
x * 2.0 + 2.0 / x - 16.0 / sin(x)
}
}
}

View File

@ -9,12 +9,10 @@ import space.kscience.kmath.expressions.MstField
import space.kscience.kmath.expressions.Symbol.Companion.x
import space.kscience.kmath.expressions.interpret
import space.kscience.kmath.operations.DoubleField
import space.kscience.kmath.operations.bindSymbol
import space.kscience.kmath.operations.invoke
fun main() {
val expr = MstField {
val x = bindSymbol(x)
x * 2.0 + number(2.0) / x - 16.0
}

View File

@ -45,11 +45,12 @@ special implementation of `Expression<T>` with implemented `invoke` function.
For example, the following builder:
```kotlin
import space.kscience.kmath.expressions.Symbol.Companion.x
import space.kscience.kmath.expressions.*
import space.kscience.kmath.operations.*
import space.kscience.kmath.asm.*
MstField { bindSymbol("x") + 2 }.compileToExpression(DoubleField)
MstField { x + 2 }.compileToExpression(DoubleField)
```
... leads to generation of bytecode, which can be decompiled to the following Java class:
@ -89,11 +90,12 @@ public final class AsmCompiledExpression_45045_0 implements Expression<Double> {
A similar feature is also available on JS.
```kotlin
import space.kscience.kmath.expressions.Symbol.Companion.x
import space.kscience.kmath.expressions.*
import space.kscience.kmath.operations.*
import space.kscience.kmath.estree.*
MstField { bindSymbol("x") + 2 }.compileToExpression(DoubleField)
MstField { x + 2 }.compileToExpression(DoubleField)
```
The code above returns expression implemented with such a JS function:
@ -108,11 +110,12 @@ JS also supports very experimental expression optimization with [WebAssembly](ht
Currently, only expressions inside `DoubleField` and `IntRing` are supported.
```kotlin
import space.kscience.kmath.expressions.Symbol.Companion.x
import space.kscience.kmath.expressions.*
import space.kscience.kmath.operations.*
import space.kscience.kmath.wasm.*
MstField { bindSymbol("x") + 2 }.compileToExpression(DoubleField)
MstField { x + 2 }.compileToExpression(DoubleField)
```
An example of emitted Wasm IR in the form of WAT:

View File

@ -16,11 +16,12 @@ special implementation of `Expression<T>` with implemented `invoke` function.
For example, the following builder:
```kotlin
import space.kscience.kmath.expressions.Symbol.Companion.x
import space.kscience.kmath.expressions.*
import space.kscience.kmath.operations.*
import space.kscience.kmath.asm.*
MstField { bindSymbol("x") + 2 }.compileToExpression(DoubleField)
MstField { x + 2 }.compileToExpression(DoubleField)
```
... leads to generation of bytecode, which can be decompiled to the following Java class:
@ -60,11 +61,12 @@ public final class AsmCompiledExpression_45045_0 implements Expression<Double> {
A similar feature is also available on JS.
```kotlin
import space.kscience.kmath.expressions.Symbol.Companion.x
import space.kscience.kmath.expressions.*
import space.kscience.kmath.operations.*
import space.kscience.kmath.estree.*
MstField { bindSymbol("x") + 2 }.compileToExpression(DoubleField)
MstField { x + 2 }.compileToExpression(DoubleField)
```
The code above returns expression implemented with such a JS function:
@ -79,11 +81,12 @@ JS also supports very experimental expression optimization with [WebAssembly](ht
Currently, only expressions inside `DoubleField` and `IntRing` are supported.
```kotlin
import space.kscience.kmath.expressions.Symbol.Companion.x
import space.kscience.kmath.expressions.*
import space.kscience.kmath.operations.*
import space.kscience.kmath.wasm.*
MstField { bindSymbol("x") + 2 }.compileToExpression(DoubleField)
MstField { x + 2 }.compileToExpression(DoubleField)
```
An example of emitted Wasm IR in the form of WAT:

View File

@ -22,7 +22,7 @@ internal class TestCompilerConsistencyWithInterpreter {
val mst = MstRing {
binaryOperationFunction("+")(
unaryOperationFunction("+")(
(bindSymbol(x) - (2.toByte() + (scale(
(x - (2.toByte() + (scale(
add(number(1), number(1)),
2.0,
) + 1.toByte()))) * 3.0 - 1.toByte()
@ -42,7 +42,7 @@ internal class TestCompilerConsistencyWithInterpreter {
fun doubleField() = runCompilerTest {
val mst = MstField {
+(3 - 2 + 2 * number(1) + 1.0) + binaryOperationFunction("+")(
(3.0 - (bindSymbol(x) + (scale(add(number(1.0), number(1.0)), 2.0) + 1.0))) * 3 - 1.0
(3.0 - (x + (scale(add(number(1.0), number(1.0)), 2.0) + 1.0))) * 3 - 1.0
+ number(1),
number(1) / 2 + number(2.0) * one,
) + zero

View File

@ -47,19 +47,19 @@ internal class TestCompilerOperations {
@Test
fun testSubtract() = runCompilerTest {
val expr = MstExtendedField { bindSymbol(x) - bindSymbol(x) }.compileToExpression(DoubleField)
val expr = MstExtendedField { x - x }.compileToExpression(DoubleField)
assertEquals(0.0, expr(x to 2.0))
}
@Test
fun testDivide() = runCompilerTest {
val expr = MstExtendedField { bindSymbol(x) / bindSymbol(x) }.compileToExpression(DoubleField)
val expr = MstExtendedField { x / x }.compileToExpression(DoubleField)
assertEquals(1.0, expr(x to 2.0))
}
@Test
fun testPower() = runCompilerTest {
val expr = MstExtendedField { bindSymbol(x) pow 2 }.compileToExpression(DoubleField)
val expr = MstExtendedField { x pow 2 }.compileToExpression(DoubleField)
assertEquals(4.0, expr(x to 2.0))
}
}

View File

@ -18,13 +18,13 @@ import kotlin.test.assertFailsWith
internal class TestCompilerVariables {
@Test
fun testVariable() = runCompilerTest {
val expr = MstRing { bindSymbol(x) }.compileToExpression(IntRing)
val expr = MstRing { x }.compileToExpression(IntRing)
assertEquals(1, expr(x to 1))
}
@Test
fun testUndefinedVariableFails() = runCompilerTest {
val expr = MstRing { bindSymbol(x) }.compileToExpression(IntRing)
val expr = MstRing { x }.compileToExpression(IntRing)
assertFailsWith<NoSuchElementException> { expr() }
}
}

View File

@ -27,11 +27,7 @@ internal sealed class WasmBuilder<T>(
lateinit var ctx: BinaryenModule
open fun visitSymbolic(mst: Symbol): ExpressionRef {
try {
algebra.bindSymbol(mst)
} catch (ignored: Throwable) {
null
}?.let { return visitNumeric(Numeric(it)) }
algebra.bindSymbolOrNull(mst)?.let { return visitNumeric(Numeric(it)) }
var idx = keys.indexOf(mst)

View File

@ -7,7 +7,6 @@ package space.kscience.kmath.ast
import space.kscience.kmath.expressions.*
import space.kscience.kmath.operations.DoubleField
import space.kscience.kmath.operations.ExtendedField
import space.kscience.kmath.operations.bindSymbol
import space.kscience.kmath.operations.invoke
import kotlin.math.sin
@ -17,18 +16,19 @@ import kotlin.time.measureTime
import space.kscience.kmath.estree.compileToExpression as estreeCompileToExpression
import space.kscience.kmath.wasm.compileToExpression as wasmCompileToExpression
// TODO move to benchmarks when https://github.com/Kotlin/kotlinx-benchmark/pull/38 or similar feature is merged
internal class TestExecutionTime {
private companion object {
private const val times = 1_000_000
private val x by symbol
private val algebra: ExtendedField<Double> = DoubleField
private val algebra = DoubleField
private val functional = DoubleField.expressionInExtendedField {
bindSymbol(x) * const(2.0) + const(2.0) / bindSymbol(x) - const(16.0) / sin(bindSymbol(x))
}
private val node = MstExtendedField {
bindSymbol(x) * number(2.0) + number(2.0) / bindSymbol(x) - number(16.0) / sin(bindSymbol(x))
x * number(2.0) + number(2.0) / x - number(16.0) / sin(x)
}
private val mst = node.toExpression(DoubleField)
@ -43,7 +43,13 @@ internal class TestExecutionTime {
// };
private val raw = Expression<Double> { args ->
args.getValue(x) * 2.0 + 2.0 / args.getValue(x) - 16.0 / sin(args.getValue(x))
val x = args[x]!!
x * 2.0 + 2.0 / x - 16.0 / sin(x)
}
private val justCalculate = { args: dynamic ->
val x = args[x].unsafeCast<Double>()
x * 2.0 + 2.0 / x - 16.0 / sin(x)
}
}
@ -51,21 +57,56 @@ internal class TestExecutionTime {
println(name)
val rng = Random(0)
var sum = 0.0
measureTime { repeat(times) { sum += expr(x to rng.nextDouble()) } }.also(::println)
measureTime {
repeat(times) { sum += expr(x to rng.nextDouble()) }
}.also(::println)
}
/**
* [Expression] created with [expressionInExtendedField].
*/
@Test
fun functionalExpression() = invokeAndSum("functional", functional)
/**
* [Expression] created with [mstExpression].
*/
@Test
fun mstExpression() = invokeAndSum("mst", mst)
/**
* [Expression] created with [wasmCompileToExpression].
*/
@Test
fun wasmExpression() = invokeAndSum("wasm", wasm)
/**
* [Expression] created with [estreeCompileToExpression].
*/
@Test
fun estreeExpression() = invokeAndSum("estree", wasm)
/**
* [Expression] implemented manually with `kotlin.math`.
*/
@Test
fun rawExpression() = invokeAndSum("raw", raw)
/**
* Direct computation w/o [Expression].
*/
@Test
fun justCalculateExpression() {
println("justCalculate")
val rng = Random(0)
var sum = 0.0
measureTime {
repeat(times) {
val arg = rng.nextDouble()
val o = js("{}")
o["x"] = arg
sum += justCalculate(o)
}
}.also(::println)
}
}

View File

@ -31,7 +31,7 @@ internal class TestWasmSpecific {
@Test
fun argsPassing() {
val res = MstExtendedField { bindSymbol(y) + bindSymbol(x).pow(10) }.compile(
val res = MstExtendedField { y + x.pow(10) }.compile(
DoubleField,
x to 2.0,
y to 100000000.0,
@ -42,7 +42,7 @@ internal class TestWasmSpecific {
@Test
fun powFunction() {
val expr = MstExtendedField { bindSymbol(x).pow(1.0 / 6.0) }.compileToExpression(DoubleField)
val expr = MstExtendedField { x.pow(1.0 / 6.0) }.compileToExpression(DoubleField)
assertEquals(0.9730585187140817, expr(x to 0.8488554755054833))
}

View File

@ -63,7 +63,7 @@ internal fun MethodVisitor.label(): Label = Label().also(::visitLabel)
* @author Iaroslav Postovalov
*/
internal tailrec fun buildName(mst: MST, collision: Int = 0): String {
val name = "kscience.kmath.asm.generated.AsmCompiledExpression_${mst.hashCode()}_$collision"
val name = "space.kscience.kmath.asm.generated.AsmCompiledExpression_${mst.hashCode()}_$collision"
try {
Class.forName(name)

View File

@ -20,8 +20,7 @@ import kotlin.test.fail
internal class AdaptingTests {
@Test
fun symbol() {
val c1 = MstNumericAlgebra.bindSymbol(x.identity)
assertEquals(x.identity, c1.toSVar<KMathNumber<Double, DoubleField>>().name)
assertEquals(x.identity, x.toSVar<KMathNumber<Double, DoubleField>>().name)
val c2 = "kitten".parseMath().toSFun<KMathNumber<Double, DoubleField>>()
if (c2 is SVar) assertTrue(c2.name == "kitten") else fail()
}
@ -46,7 +45,7 @@ internal class AdaptingTests {
@Test
fun simpleFunctionDerivative() {
val xSVar = MstNumericAlgebra.bindSymbol(x.identity).toSVar<KMathNumber<Double, DoubleField>>()
val xSVar = x.toSVar<KMathNumber<Double, DoubleField>>()
val quadratic = "x^2-4*x-44".parseMath().toSFun<KMathNumber<Double, DoubleField>>()
val actualDerivative = quadratic.d(xSVar).toMst().compileToExpression(DoubleField)
val expectedDerivative = "2*x-4".parseMath().compileToExpression(DoubleField)
@ -55,7 +54,7 @@ internal class AdaptingTests {
@Test
fun moreComplexDerivative() {
val xSVar = MstNumericAlgebra.bindSymbol(x.identity).toSVar<KMathNumber<Double, DoubleField>>()
val xSVar = x.toSVar<KMathNumber<Double, DoubleField>>()
val composition = "-sqrt(sin(x^2)-cos(x)^2-16*x)".parseMath().toSFun<KMathNumber<Double, DoubleField>>()
val actualDerivative = composition.d(xSVar).toMst().compileToExpression(DoubleField)