Skip to content

Commit

Permalink
Merge pull request #1085 from wavesplatform/EvalOpts
Browse files Browse the repository at this point in the history
Evaluator Optimizations
  • Loading branch information
ismagin authored May 28, 2018
2 parents b8547ad + 2b3e871 commit fce7a50
Show file tree
Hide file tree
Showing 18 changed files with 194 additions and 121 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -15,10 +15,8 @@ import org.openjdk.jmh.infra.Blackhole
@Warmup(iterations = 10)
@Measurement(iterations = 10)
class ScriptEstimatorBenchmark {

@Benchmark
def apply_test(st: St, bh: Blackhole): Unit = bh.consume(ScriptEstimator(st.functionCosts, st.expr))

}

object ScriptEstimatorBenchmark {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
package com.wavesplatform.lang.v1

import java.util.concurrent.TimeUnit

import com.wavesplatform.lang.TypeInfo._
import com.wavesplatform.lang.v1.ScriptEvaluatorBenchmark.St
import com.wavesplatform.lang.v1.evaluator.EvaluatorV1
import com.wavesplatform.lang.v1.evaluator.ctx.impl.PureContext
import org.openjdk.jmh.annotations._
import org.openjdk.jmh.infra.Blackhole

@OutputTimeUnit(TimeUnit.NANOSECONDS)
@BenchmarkMode(Array(Mode.AverageTime))
@Threads(4)
@Fork(1)
@Warmup(iterations = 10)
@Measurement(iterations = 10)
class ScriptEvaluatorBenchmark {
@Benchmark
def apply_test(st: St, bh: Blackhole): Unit = bh.consume(EvaluatorV1[Boolean](st.context, st.expr))
}

object ScriptEvaluatorBenchmark {
class St extends BigSum {
val context = PureContext.instance
}
}
2 changes: 2 additions & 0 deletions build.sbt
Original file line number Diff line number Diff line change
Expand Up @@ -183,6 +183,7 @@ lazy val lang =
// the following line forces scala version across all dependencies
scalaModuleInfo ~= (_.map(_.withOverrideScalaVersion(true))),
test in assembly := {},
addCompilerPlugin(Dependencies.kindProjector),
libraryDependencies ++=
Dependencies.cats ++
Dependencies.scalacheck ++
Expand Down Expand Up @@ -211,6 +212,7 @@ lazy val langJVM = lang.jvm
lazy val node = project
.in(file("."))
.settings(
addCompilerPlugin(Dependencies.kindProjector),
libraryDependencies ++=
Dependencies.network ++
Dependencies.db ++
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ import scala.util.{Left, Right, Try}

object Common {

def ev[T: TypeInfo](context: EvaluationContext = PureContext.instance, expr: EXPR): Either[(EvaluationContext, ExecutionLog, ExecutionError), T] =
def ev[T: TypeInfo](context: EvaluationContext = PureContext.instance, expr: EXPR): Either[(EvaluationContext, ExecutionError), T] =
EvaluatorV1[T](context, expr)

trait NoShrink {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import com.wavesplatform.lang.TypeInfo._
import com.wavesplatform.lang.v1.FunctionHeader
import com.wavesplatform.lang.v1.FunctionHeader.FunctionHeaderType
import com.wavesplatform.lang.v1.compiler.Terms._
import com.wavesplatform.lang.v1.evaluator.EvaluatorV1
import com.wavesplatform.lang.v1.evaluator.ctx.EvaluationContext._
import com.wavesplatform.lang.v1.evaluator.ctx._
import com.wavesplatform.lang.v1.evaluator.ctx.impl.PureContext._
Expand All @@ -20,15 +21,18 @@ import scorex.crypto.signatures.{Curve25519, PublicKey, Signature}

class EvaluatorV1Test extends PropSpec with PropertyChecks with Matchers with ScriptGen with NoShrink {

private def ev[T: TypeInfo](context: EvaluationContext = PureContext.instance, expr: EXPR): Either[(EvaluationContext, ExecutionError), T] =
EvaluatorV1[T](context, expr)
private def simpleDeclarationAndUsage(i: Int) = BLOCK(LET("x", CONST_LONG(i)), REF("x", LONG), LONG)

property("successful on very deep expressions (stack overflow check)") {
val term = (1 to 100000).foldLeft[EXPR](CONST_LONG(0))((acc, _) => FUNCTION_CALL(sumLong.header, List(acc, CONST_LONG(1)), LONG))

ev[Long](expr = term) shouldBe Right(100000)
}

property("return log and context of failed evaluation") {
val Left((ctx, log, err)) = ev[Long](
property("return error and context of failed evaluation") {
val Left((ctx, err)) = ev[Long](
expr = BLOCK(
LET("x", CONST_LONG(3)),
BLOCK(
Expand All @@ -40,16 +44,9 @@ class EvaluatorV1Test extends PropSpec with PropertyChecks with Matchers with Sc
)
)

val expectedLog =
"Evaluating BLOCK\n" ++
"LET: LET(x,CONST_LONG(3)); TYPE: LONG\n" ++
"Evaluating BLOCK\n" ++
"LET: LET(x,FUNCTION_CALL(FunctionHeader(+,List(LONG, LONG)),List(CONST_LONG(3), CONST_LONG(0)),LONG)); TYPE: LONG"

val expectedError =
"Value 'x' already defined in the scope"

log shouldBe expectedLog
err shouldBe expectedError
ctx.letDefs.contains("x") shouldBe true
}
Expand Down Expand Up @@ -342,7 +339,7 @@ class EvaluatorV1Test extends PropSpec with PropertyChecks with Matchers with Sc

private def sigVerifyTest(bodyBytes: Array[Byte],
publicKey: PublicKey,
signature: Signature): Either[(EvaluationContext, ExecutionLog, ExecutionError), Boolean] = {
signature: Signature): Either[(EvaluationContext, ExecutionError), Boolean] = {
val txType = PredefType(
"Transaction",
List(
Expand Down Expand Up @@ -393,7 +390,7 @@ class EvaluatorV1Test extends PropSpec with PropertyChecks with Matchers with Sc
for ((funcName, funcClass) <- hashFunctions) hashFuncTest(bodyBytes, funcName) shouldBe Right(ByteVector(funcClass.hash(bodyText)))
}

private def hashFuncTest(bodyBytes: Array[Byte], funcName: String): Either[(EvaluationContext, ExecutionLog, ExecutionError), ByteVector] = {
private def hashFuncTest(bodyBytes: Array[Byte], funcName: String): Either[(EvaluationContext, ExecutionError), ByteVector] = {
val context = Monoid.combineAll(
Seq(
PureContext.instance,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,8 @@ import cats.kernel.Monoid
import com.wavesplatform.lang.Common._
import com.wavesplatform.lang.TypeInfo._
import com.wavesplatform.lang.v1.compiler.{CompilerContext, CompilerV1}
import com.wavesplatform.lang.v1.evaluator.ctx.impl.PureContext
import com.wavesplatform.lang.v1.evaluator.EvaluatorV1
import com.wavesplatform.lang.v1.evaluator.ctx.impl.PureContext
import com.wavesplatform.lang.v1.evaluator.ctx.{CaseObj, EvaluationContext}
import com.wavesplatform.lang.v1.parser.Parser
import com.wavesplatform.lang.v1.testing.ScriptGen
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
package com.wavesplatform.lang

trait ExprEvaluator extends Versioned {
def apply[T: TypeInfo](ctx: version.CtxT, expr: version.ExprT): Either[(version.CtxT, ExecutionLog, ExecutionError), T]
def apply[T: TypeInfo](ctx: version.CtxT, expr: version.ExprT): Either[(version.CtxT, ExecutionError), T]
}
10 changes: 5 additions & 5 deletions lang/shared/src/main/scala/com/wavesplatform/lang/TypeInfo.scala
Original file line number Diff line number Diff line change
Expand Up @@ -85,14 +85,14 @@ object TypeInfo {
implicit val stringTypeInfo: TypeInfo[String] =
fromAnyVal(interfaces = Set(serializableTypeInfo))

implicit val objTypeInfo: TypeInfo[Obj] =
implicit val anyObjTypeInfo: TypeInfo[AnyObj] =
fromAny(interfaces = Set(productTypeInfo, serializableTypeInfo))

implicit val caseObjTypeInfo: TypeInfo[CaseObj] =
fromAny(interfaces = Set(productTypeInfo, serializableTypeInfo))
implicit val objTypeInfo: TypeInfo[Obj] =
fromAny(interfaces = Set(productTypeInfo, serializableTypeInfo, anyObjTypeInfo))

implicit val anyObjTypeInfo: TypeInfo[AnyObj] =
fromAny(interfaces = Set(productTypeInfo, serializableTypeInfo))
implicit val caseObjTypeInfo: TypeInfo[CaseObj] =
fromAny(interfaces = Set(productTypeInfo, serializableTypeInfo, anyObjTypeInfo))

implicit def optionTypeInfo[A](implicit tia: TypeInfo[A]): TypeInfo[Option[A]] =
fromAny(Set(tia), Set(productTypeInfo, serializableTypeInfo))
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
package com.wavesplatform.lang.v1.evaluator

import monix.eval.Coeval
import monix.execution.atomic.{Atomic, _}

sealed trait CoevalRef[A] {
def read: Coeval[A]
def write(a: A): Coeval[Unit]
}

object CoevalRef {
def of[A, R <: Atomic[A]](a: A)(implicit ab: AtomicBuilder[A, R]): CoevalRef[A] = {
new CoevalRef[A] {
private val atom: Atomic[A] = Atomic(a)
override def read: Coeval[A] = Coeval.delay(atom.get)
override def write(a: A): Coeval[Unit] = Coeval.delay(atom.set(a))
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
package com.wavesplatform.lang.v1.evaluator

import cats.Monad
import cats.data.{EitherT, Kleisli}
import cats.implicits._
import com.wavesplatform.lang.v1.evaluator.ctx.EvaluationContext
import com.wavesplatform.lang.{ExecutionError, TrampolinedExecResult}
import monix.eval.Coeval

final case class EvalM[A](inner: Kleisli[Coeval, CoevalRef[EvaluationContext], Either[ExecutionError, A]]) {
def ter(ctx: EvaluationContext): TrampolinedExecResult[A] = {
val atom = CoevalRef.of(ctx)

EitherT(inner.run(atom))
}

def run(ctx: EvaluationContext): Either[(EvaluationContext, ExecutionError), A] = {
val atom = CoevalRef.of(ctx)

val action = inner
.run(atom)

(for {
result <- action
lastCtx <- atom.read
} yield result.left.map(err => (lastCtx, err))).value
}
}

object EvalM {

private type MM[A] = Kleisli[Coeval, CoevalRef[EvaluationContext], A]
private val M: Monad[MM] = implicitly

implicit val monadInstance: Monad[EvalM] = new Monad[EvalM] {
override def pure[A](x: A): EvalM[A] =
EvalM(Kleisli.liftF[Coeval, CoevalRef[EvaluationContext], Either[ExecutionError, A]](Coeval.evalOnce(Right(x))))

override def flatMap[A, B](fa: EvalM[A])(f: A => EvalM[B]): EvalM[B] = {
EvalM(fa.inner.flatMap({
case Right(v) => f(v).inner
case Left(err) => Kleisli.pure(err.asLeft[B])
}))
}

override def tailRecM[A, B](a: A)(f: A => EvalM[Either[A, B]]): EvalM[B] = {
EvalM(M.tailRecM(a)(f andThen (_.inner.map {
case Left(err) => Right(Left(err))
case Right(Left(lv)) => Left(lv)
case Right(Right(rv)) => Right(Right(rv))
})))
}
}

def getContext: EvalM[EvaluationContext] =
EvalM(Kleisli[Coeval, CoevalRef[EvaluationContext], Either[ExecutionError, EvaluationContext]](ref => {
ref.read.map(_.asRight[ExecutionError])
}))

def setContext(ctx: EvaluationContext): EvalM[Unit] =
EvalM(Kleisli[Coeval, CoevalRef[EvaluationContext], Either[ExecutionError, Unit]](ref => {
ref.write(ctx).map(_.asRight[ExecutionError])
}))

def updateContext(f: EvaluationContext => EvaluationContext): EvalM[Unit] = getContext >>= (f andThen setContext)

def liftValue[A](a: A): EvalM[A] = monadInstance.pure(a)

def liftError[A](err: ExecutionError): EvalM[A] =
EvalM(Kleisli[Coeval, CoevalRef[EvaluationContext], Either[ExecutionError, A]](_ => Coeval.delay(err.asLeft[A])))

def liftTER[A](ter: Coeval[Either[ExecutionError, A]]): EvalM[A] =
EvalM(Kleisli[Coeval, CoevalRef[EvaluationContext], Either[ExecutionError, A]](_ => ter))
}
Loading

0 comments on commit fce7a50

Please sign in to comment.