Skip to content

Commit

Permalink
Add many more optimizations
Browse files Browse the repository at this point in the history
Most of these do nothing. Weird.
  • Loading branch information
noelwelsh committed Dec 8, 2023
1 parent 4ca32ba commit b0795f1
Show file tree
Hide file tree
Showing 10 changed files with 381 additions and 25 deletions.
13 changes: 8 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
```
23 changes: 22 additions & 1 deletion benchmarks/src/main/scala/arithmetic/FibonacciBenchmark.scala
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -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 = {
Expand All @@ -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)
}
}
9 changes: 7 additions & 2 deletions build.sbt
Original file line number Diff line number Diff line change
Expand Up @@ -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*")
)
Expand Down
123 changes: 123 additions & 0 deletions core/src/main/scala/arithmetic/ByteCode.scala
Original file line number Diff line number Diff line change
@@ -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)
}
}
}
34 changes: 17 additions & 17 deletions core/src/main/scala/arithmetic/OptimizedStack.scala
Original file line number Diff line number Diff line change
Expand Up @@ -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)
}

Expand Down
93 changes: 93 additions & 0 deletions core/src/main/scala/arithmetic/StackCaching.scala
Original file line number Diff line number Diff line change
@@ -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
}
}
}
Loading

0 comments on commit b0795f1

Please sign in to comment.