From b0795f192495275d162dca0bd40ef8b17d4febd0 Mon Sep 17 00:00:00 2001 From: Noel Welsh Date: Fri, 8 Dec 2023 16:55:55 +0000 Subject: [PATCH] Add many more optimizations Most of these do nothing. Weird. --- README.md | 13 +- .../scala/arithmetic/FibonacciBenchmark.scala | 23 +++- build.sbt | 9 +- core/src/main/scala/arithmetic/ByteCode.scala | 123 ++++++++++++++++++ .../scala/arithmetic/OptimizedStack.scala | 34 ++--- .../main/scala/arithmetic/StackCaching.scala | 93 +++++++++++++ .../scala/arithmetic/SuperInstruction.scala | 102 +++++++++++++++ .../test/scala/arithmetic/ByteCodeSuite.scala | 3 + .../scala/arithmetic/StackCachingSuite.scala | 3 + .../arithmetic/SuperInstructionSuite.scala | 3 + 10 files changed, 381 insertions(+), 25 deletions(-) create mode 100644 core/src/main/scala/arithmetic/ByteCode.scala create mode 100644 core/src/main/scala/arithmetic/StackCaching.scala create mode 100644 core/src/main/scala/arithmetic/SuperInstruction.scala create mode 100644 core/src/test/scala/arithmetic/ByteCodeSuite.scala create mode 100644 core/src/test/scala/arithmetic/StackCachingSuite.scala create mode 100644 core/src/test/scala/arithmetic/SuperInstructionSuite.scala diff --git a/README.md b/README.md index 3aadb90..2926268 100644 --- a/README.md +++ b/README.md @@ -10,9 +10,12 @@ A series of implementatinos of stack machines to optimize evaluation of arithmet Example results ``` -[info] FibonnaciBenchmark.baseFibBenchmark thrpt 5 2465.799 ± 322.013 ops/s -[info] FibonnaciBenchmark.basicStackFibBenchmark thrpt 5 456.614 ± 123.451 ops/s -[info] FibonnaciBenchmark.optimizedStack2FibBenchmark thrpt 5 3321.844 ± 46.575 ops/s -[info] FibonnaciBenchmark.optimizedStack3FibBenchmark thrpt 5 980.858 ± 604.299 ops/s -[info] FibonnaciBenchmark.optimizedStackFibBenchmark thrpt 5 3312.489 ± 165.045 ops/s +[info] FibonnaciBenchmark.baseFibBenchmark thrpt 25 2748.000 ± 17.741 ops/s +[info] FibonnaciBenchmark.basicStackFibBenchmark thrpt 25 644.858 ± 32.125 ops/s +[info] FibonnaciBenchmark.byteCodeFibBenchmark thrpt 25 1675.357 ± 5.391 ops/s +[info] FibonnaciBenchmark.optimizedStack2FibBenchmark thrpt 25 3563.952 ± 71.645 ops/s +[info] FibonnaciBenchmark.optimizedStack3FibBenchmark thrpt 25 3540.126 ± 51.814 ops/s +[info] FibonnaciBenchmark.optimizedStackFibBenchmark thrpt 25 3630.538 ± 12.488 ops/s +[info] FibonnaciBenchmark.stackCachingFibBenchmark thrpt 25 3508.967 ± 234.853 ops/s +[info] FibonnaciBenchmark.superInstructionFibBenchmark thrpt 25 3839.907 ± 452.660 ops/s ``` diff --git a/benchmarks/src/main/scala/arithmetic/FibonacciBenchmark.scala b/benchmarks/src/main/scala/arithmetic/FibonacciBenchmark.scala index a6d007e..93889e7 100644 --- a/benchmarks/src/main/scala/arithmetic/FibonacciBenchmark.scala +++ b/benchmarks/src/main/scala/arithmetic/FibonacciBenchmark.scala @@ -22,7 +22,7 @@ import java.util.concurrent.TimeUnit.SECONDS @State(Scope.Benchmark) @Warmup(iterations = 5, time = 1, timeUnit = SECONDS) -@Measurement(iterations = 1, time = 1, timeUnit = SECONDS) +@Measurement(iterations = 5, time = 1, timeUnit = SECONDS) class FibonnaciBenchmark { def makeFibonacci[E <: Expression[E]]( n: Int, @@ -48,6 +48,12 @@ class FibonnaciBenchmark { makeFibonacci(nFib, OptimizedStack2.Expression).compile val optimizedStack3Fib = makeFibonacci(nFib, OptimizedStack3.Expression).compile + val stackCachingFib = + makeFibonacci(nFib, StackCaching.Expression).compile + val superInstructionFib = + makeFibonacci(nFib, SuperInstruction.Expression).compile + val byteCodeFib = + makeFibonacci(nFib, ByteCode.Expression).compile @Benchmark def baseFibBenchmark(): Unit = { @@ -73,4 +79,19 @@ class FibonnaciBenchmark { def optimizedStack3FibBenchmark(): Unit = { assert(optimizedStack3Fib.eval == expected) } + + @Benchmark + def stackCachingFibBenchmark(): Unit = { + assert(stackCachingFib.eval == expected) + } + + @Benchmark + def superInstructionFibBenchmark(): Unit = { + assert(superInstructionFib.eval == expected) + } + + @Benchmark + def byteCodeFibBenchmark(): Unit = { + assert(byteCodeFib.eval == expected) + } } diff --git a/build.sbt b/build.sbt index 0e74ca3..7202ee6 100644 --- a/build.sbt +++ b/build.sbt @@ -39,8 +39,13 @@ lazy val core = project.in(file("core")).settings(commonSettings) lazy val benchmarks = project .in(file("benchmarks")) .settings( - // javaOptions ++= Seq("-Xbatch", - // "-XX:+UnlockDiagnosticVMOptions", + javaOptions ++= Seq("-Xbatch", + "-XX:+UnlockExperimentalVMOptions", + "-XX:+UnlockDiagnosticVMOptions", + "-XX:-TieredCompilation") + // "-XX:MaxInlineSize=300") + // "-XX:+LogCompilation") + // "-XX:CompileThreshold=1000") // "-XX:CompileCommand=print,arithmetic/OptimizedStack2$StackMachine.eval", // "-XX:CompileCommand=print,arithmetic/OptimizedStack$StackMachine.loop*") ) diff --git a/core/src/main/scala/arithmetic/ByteCode.scala b/core/src/main/scala/arithmetic/ByteCode.scala new file mode 100644 index 0000000..f8decd1 --- /dev/null +++ b/core/src/main/scala/arithmetic/ByteCode.scala @@ -0,0 +1,123 @@ +package arithmetic + +import java.nio.ByteBuffer + +object ByteCode { + + enum Expression extends arithmetic.Expression[Expression] { + case Literal(value: Double) + case Addition(left: Expression, right: Expression) + case Subtraction(left: Expression, right: Expression) + case Multiplication(left: Expression, right: Expression) + case Division(left: Expression, right: Expression) + + def +(that: Expression): Expression = Addition(this, that) + def *(that: Expression): Expression = Multiplication(this, that) + def -(that: Expression): Expression = Subtraction(this, that) + def /(that: Expression): Expression = Division(this, that) + + def compile: Program = { + val p = Array.ofDim[Byte](16777216) + val buffer = ByteBuffer.wrap(p) + var limit = 0 + def loop(expr: Expression): Unit = + expr match { + case Literal(value) => + buffer.put(Op.Lit.ordinal.toByte) + buffer.putDouble(value) + limit = limit + 8 + 1 + case Addition(left, right) => + loop(left) + loop(right) + buffer.put(Op.Add.ordinal.toByte) + limit = limit + 1 + case Subtraction(left, right) => + loop(left) + loop(right) + buffer.put(Op.Sub.ordinal.toByte) + limit = limit + 1 + case Multiplication(left, right) => + loop(left) + loop(right) + buffer.put(Op.Mul.ordinal.toByte) + limit = limit + 1 + case Division(left, right) => + loop(left) + loop(right) + buffer.put(Op.Div.ordinal.toByte) + limit = limit + 1 + } + + loop(this) + Program(buffer, limit) + } + + def eval: Double = compile.eval + } + object Expression extends arithmetic.ExpressionConstructors[Expression] { + def literal(value: Double): Expression = Literal(value) + } + + enum Op { + case Lit + case Add + case Sub + case Mul + case Div + } + + final case class Program(program: ByteBuffer, limit: Int) { + val machine = new StackMachine(program, limit) + + def eval: Double = machine.eval + } + + final case class StackMachine(program: ByteBuffer, limit: Int) { + // The data stack + private val stack: Array[Double] = Array.ofDim[Double](256) + + object code { + val lit = Op.Lit.ordinal.toByte + val add = Op.Add.ordinal.toByte + val sub = Op.Sub.ordinal.toByte + val mul = Op.Mul.ordinal.toByte + val div = Op.Div.ordinal.toByte + } + + final def eval: Double = { + val p = program.array() + // sp points to first free element on the stack + // stack(sp - 1) is the first element + def loop(sp: Int, pc: Int): Double = + if (pc == limit) stack(sp - 1) + else + p(pc) match { + case code.lit => + stack(sp) = program.getDouble(pc + 1) + loop(sp + 1, pc + 1 + 8) + case code.add => + val a = stack(sp - 1) + val b = stack(sp - 2) + stack(sp - 2) = (a + b) + loop(sp - 1, pc + 1) + case code.sub => + val a = stack(sp - 1) + val b = stack(sp - 2) + stack(sp - 2) = (a - b) + loop(sp - 1, pc + 1) + case code.mul => + val a = stack(sp - 1) + val b = stack(sp - 2) + stack(sp - 2) = (a * b) + loop(sp - 1, pc + 1) + case code.div => + val a = stack(sp - 1) + val b = stack(sp - 2) + stack(sp - 2) = (a / b) + loop(sp - 1, pc + 1) + } + + loop(0, 0) + } + } +} diff --git a/core/src/main/scala/arithmetic/OptimizedStack.scala b/core/src/main/scala/arithmetic/OptimizedStack.scala index e22eeb6..e3d064f 100644 --- a/core/src/main/scala/arithmetic/OptimizedStack.scala +++ b/core/src/main/scala/arithmetic/OptimizedStack.scala @@ -51,39 +51,39 @@ object OptimizedStack { def eval: Double = machine.eval } - class StackMachine(program: Array[Op]) { + final class StackMachine(program: Array[Op]) { // The data stack - private val data: Array[Double] = Array.ofDim[Double](256) + private val stack: Array[Double] = Array.ofDim[Double](256) final def eval: Double = { // sp points to first free element on the stack - // data(sp - 1) is the first element + // stack(sp - 1) is the first element def loop(sp: Int, pc: Int): Double = - if (pc == program.size) data(sp - 1) + if (pc == program.size) stack(sp - 1) else program(pc) match { case Op.Lit(value) => - data(sp) = value + stack(sp) = value loop(sp + 1, pc + 1) case Op.Add => - val a = data(sp - 1) - val b = data(sp - 2) - data(sp - 2) = (a + b) + val a = stack(sp - 1) + val b = stack(sp - 2) + stack(sp - 2) = (a + b) loop(sp - 1, pc + 1) case Op.Sub => - val a = data(sp - 1) - val b = data(sp - 2) - data(sp - 2) = (a - b) + val a = stack(sp - 1) + val b = stack(sp - 2) + stack(sp - 2) = (a - b) loop(sp - 1, pc + 1) case Op.Mul => - val a = data(sp - 1) - val b = data(sp - 2) - data(sp - 2) = (a * b) + val a = stack(sp - 1) + val b = stack(sp - 2) + stack(sp - 2) = (a * b) loop(sp - 1, pc + 1) case Op.Div => - val a = data(sp - 1) - val b = data(sp - 2) - data(sp - 2) = (a / b) + val a = stack(sp - 1) + val b = stack(sp - 2) + stack(sp - 2) = (a / b) loop(sp - 1, pc + 1) } diff --git a/core/src/main/scala/arithmetic/StackCaching.scala b/core/src/main/scala/arithmetic/StackCaching.scala new file mode 100644 index 0000000..8b7f7da --- /dev/null +++ b/core/src/main/scala/arithmetic/StackCaching.scala @@ -0,0 +1,93 @@ +package arithmetic + +object StackCaching { + + enum Expression extends arithmetic.Expression[Expression] { + case Literal(value: Double) + case Addition(left: Expression, right: Expression) + case Subtraction(left: Expression, right: Expression) + case Multiplication(left: Expression, right: Expression) + case Division(left: Expression, right: Expression) + + def +(that: Expression): Expression = Addition(this, that) + def *(that: Expression): Expression = Multiplication(this, that) + def -(that: Expression): Expression = Subtraction(this, that) + def /(that: Expression): Expression = Division(this, that) + + def compile: Program = { + def loop(expr: Expression): List[Op] = + expr match { + case Literal(value) => List(Op.Lit(value)) + case Addition(left, right) => + loop(left) ++ loop(right) ++ List(Op.Add) + case Subtraction(left, right) => + loop(left) ++ loop(right) ++ List(Op.Sub) + case Multiplication(left, right) => + loop(left) ++ loop(right) ++ List(Op.Mul) + case Division(left, right) => + loop(left) ++ loop(right) ++ List(Op.Div) + } + + Program(loop(this)) + } + + def eval: Double = compile.eval + } + object Expression extends arithmetic.ExpressionConstructors[Expression] { + def literal(value: Double): Expression = Literal(value) + } + + enum Op { + case Lit(value: Double) + case Add + case Sub + case Mul + case Div + } + + final case class Program(program: List[Op]) { + val machine = new StackMachine(program.toArray) + + def eval: Double = machine.eval + } + + final class StackMachine(program: Array[Op]) { + // The data stack + private val stack: Array[Double] = Array.ofDim[Double](256) + + final def eval: Double = { + // The top of the stack + var top: Double = 0.0 + var sp: Int = 0 + var pc: Int = 0 + + val size = program.size + while (pc < size) { + program(pc) match { + case Op.Lit(value) => + stack(sp) = top + top = value + sp = sp + 1 + pc = pc + 1 + case Op.Add => + sp = sp - 1 + top = top + stack(sp) + pc = pc + 1 + case Op.Sub => + sp = sp - 1 + top = top - stack(sp) + pc = pc + 1 + case Op.Mul => + sp = sp - 1 + top = top * stack(sp) + pc = pc + 1 + case Op.Div => + sp = sp - 1 + top = top / stack(sp) + pc = pc + 1 + } + } + top + } + } +} diff --git a/core/src/main/scala/arithmetic/SuperInstruction.scala b/core/src/main/scala/arithmetic/SuperInstruction.scala new file mode 100644 index 0000000..723b03d --- /dev/null +++ b/core/src/main/scala/arithmetic/SuperInstruction.scala @@ -0,0 +1,102 @@ +package arithmetic + +object SuperInstruction { + + enum Expression extends arithmetic.Expression[Expression] { + case Literal(value: Double) + case Addition(left: Expression, right: Expression) + case Subtraction(left: Expression, right: Expression) + case Multiplication(left: Expression, right: Expression) + case Division(left: Expression, right: Expression) + + def +(that: Expression): Expression = Addition(this, that) + def *(that: Expression): Expression = Multiplication(this, that) + def -(that: Expression): Expression = Subtraction(this, that) + def /(that: Expression): Expression = Division(this, that) + + def compile: Program = { + def loop(expr: Expression): List[Op] = + expr match { + // case Addition(Literal(0.0), expr) => loop(expr) + // case Addition(expr, Literal(0.0)) => loop(expr) + case Addition(Literal(v), expr) => loop(expr) ++ List(Op.AddLit(v)) + case Addition(expr, Literal(v)) => loop(expr) ++ List(Op.AddLit(v)) + case Literal(value) => List(Op.Lit(value)) + case Addition(left, right) => + loop(left) ++ loop(right) ++ List(Op.Add) + case Subtraction(left, right) => + loop(left) ++ loop(right) ++ List(Op.Sub) + case Multiplication(left, right) => + loop(left) ++ loop(right) ++ List(Op.Mul) + case Division(left, right) => + loop(left) ++ loop(right) ++ List(Op.Div) + } + + Program(loop(this)) + } + + def eval: Double = compile.eval + } + object Expression extends arithmetic.ExpressionConstructors[Expression] { + def literal(value: Double): Expression = Literal(value) + } + + enum Op { + case Lit(value: Double) + case AddLit(value: Double) + case Add + case Sub + case Mul + case Div + } + + final case class Program(program: List[Op]) { + val machine = new StackMachine(program.toArray) + + def eval: Double = machine.eval + } + + final class StackMachine(program: Array[Op]) { + // The data stack + private val stack: Array[Double] = Array.ofDim[Double](256) + + final def eval: Double = { + // sp points to first free element on the stack + // stack(sp - 1) is the first element + def loop(sp: Int, pc: Int): Double = + if (pc == program.size) stack(sp - 1) + else + program(pc) match { + case op: Op.Lit => + stack(sp) = op.value + loop(sp + 1, pc + 1) + case op: Op.AddLit => + val a = stack(sp - 1) + stack(sp - 1) = a + op.value + loop(sp, pc + 1) + case Op.Add => + val a = stack(sp - 1) + val b = stack(sp - 2) + stack(sp - 2) = (a + b) + loop(sp - 1, pc + 1) + case Op.Sub => + val a = stack(sp - 1) + val b = stack(sp - 2) + stack(sp - 2) = (a - b) + loop(sp - 1, pc + 1) + case Op.Mul => + val a = stack(sp - 1) + val b = stack(sp - 2) + stack(sp - 2) = (a * b) + loop(sp - 1, pc + 1) + case Op.Div => + val a = stack(sp - 1) + val b = stack(sp - 2) + stack(sp - 2) = (a / b) + loop(sp - 1, pc + 1) + } + + loop(0, 0) + } + } +} diff --git a/core/src/test/scala/arithmetic/ByteCodeSuite.scala b/core/src/test/scala/arithmetic/ByteCodeSuite.scala new file mode 100644 index 0000000..ae9e590 --- /dev/null +++ b/core/src/test/scala/arithmetic/ByteCodeSuite.scala @@ -0,0 +1,3 @@ +package arithmetic + +class ByteCodeSuite extends ArithmeticSuite(ByteCode.Expression) diff --git a/core/src/test/scala/arithmetic/StackCachingSuite.scala b/core/src/test/scala/arithmetic/StackCachingSuite.scala new file mode 100644 index 0000000..9ed6b4a --- /dev/null +++ b/core/src/test/scala/arithmetic/StackCachingSuite.scala @@ -0,0 +1,3 @@ +package arithmetic + +class StackCachingSuite extends ArithmeticSuite(StackCaching.Expression) diff --git a/core/src/test/scala/arithmetic/SuperInstructionSuite.scala b/core/src/test/scala/arithmetic/SuperInstructionSuite.scala new file mode 100644 index 0000000..0822f54 --- /dev/null +++ b/core/src/test/scala/arithmetic/SuperInstructionSuite.scala @@ -0,0 +1,3 @@ +package arithmetic + +class SuperInstructionSuite extends ArithmeticSuite(SuperInstruction.Expression)