Skip to content

Commit

Permalink
Merge pull request #690 from ScorexFoundation/optimize-mem-footprint
Browse files Browse the repository at this point in the history
Optimize memory footprint
  • Loading branch information
aslesarenko authored Sep 28, 2020
2 parents 75d2a9a + 31cbe75 commit 6c691d1
Show file tree
Hide file tree
Showing 35 changed files with 560 additions and 339 deletions.
24 changes: 14 additions & 10 deletions common/src/main/scala/scalan/TypeDesc.scala
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,9 @@ abstract class RType[A] {
/** Returns true is data size of `x: A` is the same for all `x`.
* This is useful optimizations of calculating sizes of collections. */
def isConstantSize: Boolean

/** Creates empty immutable array of this type. */
def emptyArray: Array[A] = Array.empty[A](classTag)
}

object RType {
Expand Down Expand Up @@ -61,7 +64,8 @@ object RType {
}

/** Descriptor used to represent primitive types. */
case class PrimitiveType[A](classTag: ClassTag[A]) extends RType[A] {
case class PrimitiveType[A](classTag: ClassTag[A],
override val emptyArray: Array[A]) extends RType[A] {
override def name: String = classTag.toString()
/** We assume all primitive types have inhabitants of the same size. */
override def isConstantSize: Boolean = true
Expand All @@ -71,15 +75,15 @@ object RType {
val AnyRefType : RType[AnyRef] = GeneralType[AnyRef] (ClassTag.AnyRef)
val NothingType : RType[Nothing] = GeneralType[Nothing] (ClassTag.Nothing)

implicit val BooleanType : RType[Boolean] = PrimitiveType[Boolean] (ClassTag.Boolean)
implicit val ByteType : RType[Byte] = PrimitiveType[Byte] (ClassTag.Byte)
implicit val ShortType : RType[Short] = PrimitiveType[Short] (ClassTag.Short)
implicit val IntType : RType[Int] = PrimitiveType[Int] (ClassTag.Int)
implicit val LongType : RType[Long] = PrimitiveType[Long] (ClassTag.Long)
implicit val CharType : RType[Char] = PrimitiveType[Char] (ClassTag.Char)
implicit val FloatType : RType[Float] = PrimitiveType[Float] (ClassTag.Float)
implicit val DoubleType : RType[Double] = PrimitiveType[Double] (ClassTag.Double)
implicit val UnitType : RType[Unit] = PrimitiveType[Unit] (ClassTag.Unit)
implicit val BooleanType : RType[Boolean] = PrimitiveType[Boolean] (ClassTag.Boolean, Array.emptyBooleanArray)
implicit val ByteType : RType[Byte] = PrimitiveType[Byte] (ClassTag.Byte, Array.emptyByteArray)
implicit val ShortType : RType[Short] = PrimitiveType[Short] (ClassTag.Short, Array.emptyShortArray)
implicit val IntType : RType[Int] = PrimitiveType[Int] (ClassTag.Int, Array.emptyIntArray)
implicit val LongType : RType[Long] = PrimitiveType[Long] (ClassTag.Long, Array.emptyLongArray)
implicit val CharType : RType[Char] = PrimitiveType[Char] (ClassTag.Char, Array.emptyCharArray)
implicit val FloatType : RType[Float] = PrimitiveType[Float] (ClassTag.Float, Array.emptyFloatArray)
implicit val DoubleType : RType[Double] = PrimitiveType[Double] (ClassTag.Double, Array.emptyDoubleArray)
implicit val UnitType : RType[Unit] = PrimitiveType[Unit] (ClassTag.Unit, Array[Unit]()(ClassTag.Unit))

/** Descriptor of the type A narrowed to the single inhabitant `value`. */
case class SingletonType[A](value: A, classTag: ClassTag[A])() extends RType[A] {
Expand Down
10 changes: 5 additions & 5 deletions core/src/main/scala/scalan/Entities.scala
Original file line number Diff line number Diff line change
@@ -1,14 +1,14 @@
package scalan

import java.util.Objects

import scala.annotation.tailrec
import scala.language.higherKinds
import scalan.util.ReflectionUtil.ClassOps

/** A slice in the Scalan cake with base classes for various descriptors. */
trait Entities extends TypeDescs { self: Scalan =>

/** Base class for all descriptors of staged traits. */
/** Base class for all descriptors of staged traits.
* See derived classes in `impl` packages.
*/
abstract class EntityElem[A] extends Elem[A] with scala.Equals {
/** Optional parent type in inheritance hierarchy */
def parent: Option[Elem[_]] = None
Expand All @@ -28,7 +28,7 @@ trait Entities extends TypeDescs { self: Scalan =>
case _ => false
})

override def hashCode = Objects.hash(getClass, typeArgsDescs)
override def hashCode = getClass.hashCode() * 31 + typeArgsDescs.hashCode()
}

/** Base class for all descriptors of staged traits with one type parameter. */
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -305,7 +305,7 @@ class CollOverArrayBuilder extends CollBuilder {
val rs = emptyColl(pt.tSnd)
asColl[T](pairColl(ls, rs))
case _ =>
new CollOverArray[T](Array[T]())
new CollOverArray[T](cT.emptyArray)
}

@NeverInline
Expand Down
16 changes: 16 additions & 0 deletions sigma-api/src/main/scala/special/sigma/SigmaDsl.scala
Original file line number Diff line number Diff line change
Expand Up @@ -9,18 +9,34 @@ import scalan._

@scalan.Liftable
trait CostModel {
/** Cost of accessing SELF box and/or each item in INPUTS, OUTPUTS, dataInputs. */
def AccessBox: Int // costOf("AccessBox: Context => Box")

// TODO refactor: remove not used
def AccessAvlTree: Int // costOf("AccessAvlTree: Context => AvlTree")

/** Cost of accessing context variable (`getVar` operation in ErgoScript). */
def GetVar: Int // costOf("ContextVar: (Context, Byte) => Option[T]")

// TODO refactor: remove not used
def DeserializeVar: Int // costOf("DeserializeVar: (Context, Byte) => Option[T]")

/** Cost of accessing register in a box (e.g. `R4[Int]` operation in ErgoScript). */
def GetRegister: Int // costOf("AccessRegister: (Box,Byte) => Option[T]")

// TODO refactor: remove not used
def DeserializeRegister: Int // costOf("DeserializeRegister: (Box,Byte) => Option[T]")

/** Cost of accessing a property of an object like Header or AvlTree. */
def SelectField: Int // costOf("SelectField")

// TODO refactor: remove not used
def CollectionConst: Int // costOf("Const: () => Array[IV]")

// TODO refactor: remove not used
def AccessKiloByteOfData: Int // costOf("AccessKiloByteOfData")

// TODO refactor: remove not used
/** Size of public key in bytes */
def PubKeySize: Long
}
Expand Down
26 changes: 16 additions & 10 deletions sigmastate/src/main/scala/org/ergoplatform/ErgoLikeContext.scala
Original file line number Diff line number Diff line change
Expand Up @@ -194,58 +194,64 @@ object ErgoLikeContext {
/** When interpreted evaluates to a ByteArrayConstant built from Context.minerPubkey */
case object MinerPubkey extends NotReadyValueByteArray with ValueCompanion {
override def opCode: OpCode = OpCodes.MinerPubkeyCode
def opType = SFunc(SContext, SCollection.SByteArray)
override val opType = SFunc(SContext, SCollection.SByteArray)
override def companion = this
}

/** When interpreted evaluates to a IntConstant built from Context.currentHeight */
case object Height extends NotReadyValueInt with ValueCompanion {
override def companion = this
override def opCode: OpCode = OpCodes.HeightCode
def opType = SFunc(SContext, SInt)
override val opType = SFunc(SContext, SInt)
}

/** When interpreted evaluates to a collection of BoxConstant built from Context.boxesToSpend */
case object Inputs extends LazyCollection[SBox.type] with ValueCompanion {
override def companion = this
override def opCode: OpCode = OpCodes.InputsCode
val tpe = SCollection(SBox)
def opType = SFunc(SContext, tpe)
override def tpe = SCollection.SBoxArray
override val opType = SFunc(SContext, tpe)
}

/** When interpreted evaluates to a collection of BoxConstant built from Context.spendingTransaction.outputs */
case object Outputs extends LazyCollection[SBox.type] with ValueCompanion {
override def companion = this
override def opCode: OpCode = OpCodes.OutputsCode
val tpe = SCollection(SBox)
def opType = SFunc(SContext, tpe)
override def tpe = SCollection.SBoxArray
override val opType = SFunc(SContext, tpe)
}

/** When interpreted evaluates to a AvlTreeConstant built from Context.lastBlockUtxoRoot */
case object LastBlockUtxoRootHash extends NotReadyValueAvlTree with ValueCompanion {
override def companion = this
override def opCode: OpCode = OpCodes.LastBlockUtxoRootHashCode
def opType = SFunc(SContext, tpe)
override val opType = SFunc(SContext, tpe)
}


/** When interpreted evaluates to a BoxConstant built from context.boxesToSpend(context.selfIndex) */
case object Self extends NotReadyValueBox with ValueCompanion {
override def companion = this
override def opCode: OpCode = OpCodes.SelfCode
def opType = SFunc(SContext, SBox)
override val opType = SFunc(SContext, SBox)
}

/** When interpreted evaluates to the singleton instance of [[special.sigma.Context]].
* Corresponds to `CONTEXT` variable in ErgoScript which can be used like `CONTEXT.headers`.
*/
case object Context extends NotReadyValue[SContext.type] with ValueCompanion {
override def companion = this
override def opCode: OpCode = OpCodes.ContextCode
override def tpe: SContext.type = SContext
override def opType: SFunc = SFunc(SUnit, SContext)
override val opType: SFunc = SFunc(SUnit, SContext)
}

/** When interpreted evaluates to the singleton instance of [[special.sigma.SigmaDslBuilder]].
* Corresponds to `Global` variable in ErgoScript which can be used like `Global.groupGenerator`.
*/
case object Global extends NotReadyValue[SGlobal.type] with ValueCompanion {
override def companion = this
override def opCode: OpCode = OpCodes.GlobalCode
override def tpe: SGlobal.type = SGlobal
override def opType: SFunc = SFunc(SUnit, SGlobal)
override val opType: SFunc = SFunc(SUnit, SGlobal)
}
14 changes: 5 additions & 9 deletions sigmastate/src/main/scala/sigmastate/Values.scala
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,8 @@ import java.util.Objects

import org.bitbucket.inkytonik.kiama.relation.Tree
import org.bitbucket.inkytonik.kiama.rewriting.Rewriter.{strategy, everywherebu}
import org.ergoplatform.ErgoLikeContext
import org.ergoplatform.validation.ValidationException
import scalan.{Nullable, RType}
import scorex.crypto.authds.{ADDigest, SerializedAdProof}
import scorex.crypto.authds.avltree.batch.BatchAVLVerifier
import scorex.crypto.hash.{Digest32, Blake2b256}
import scalan.util.CollectionUtil._
import sigmastate.SCollection.{SIntArray, SByteArray}
import sigmastate.interpreter.CryptoConstants.EcPointType
Expand All @@ -36,7 +32,7 @@ import sigmastate.lang.DefaultSigmaBuilder._
import sigmastate.serialization.ErgoTreeSerializer.DefaultSerializer
import sigmastate.serialization.transformers.ProveDHTupleSerializer
import sigmastate.utils.{SigmaByteReader, SigmaByteWriter}
import special.sigma.{AnyValue, AvlTree, PreHeader, Header, _}
import special.sigma.{AvlTree, PreHeader, Header, _}
import sigmastate.lang.SourceContext
import special.collection.Coll

Expand Down Expand Up @@ -153,7 +149,7 @@ object Values {
ft.getGenericType
case _ => tpe
}
SFunc(Vector(), resType)
SFunc(mutable.WrappedArray.empty, resType)
}
}

Expand Down Expand Up @@ -814,9 +810,9 @@ object Values {
*/
case class FuncValue(args: IndexedSeq[(Int,SType)], body: Value[SType]) extends NotReadyValue[SFunc] {
override def companion = FuncValue
lazy val tpe: SFunc = SFunc(args.map(_._2), body.tpe)
lazy val tpe: SFunc = SFunc(args.toArray.map(_._2), body.tpe)
/** This is not used as operation, but rather to form a program structure */
override def opType: SFunc = SFunc(Vector(), tpe)
override def opType: SFunc = SFunc(mutable.WrappedArray.empty, tpe)
}
object FuncValue extends ValueCompanion {
override def opCode: OpCode = FuncValueCode
Expand Down Expand Up @@ -1018,7 +1014,7 @@ object Values {

def substConstants(root: SValue, constants: IndexedSeq[Constant[SType]]): SValue = {
val store = new ConstantStore(constants)
val substRule = strategy[Value[_ <: SType]] {
val substRule = strategy[Any] {
case ph: ConstantPlaceholder[_] =>
Some(store.get(ph.id))
case _ => None
Expand Down
54 changes: 43 additions & 11 deletions sigmastate/src/main/scala/sigmastate/eval/CostingDataContext.scala
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ import sigmastate.serialization.ErgoTreeSerializer.DefaultSerializer
import sigmastate.serialization.{SigmaSerializer, GroupElementSerializer}
import special.Types.TupleType

import scala.collection.mutable
import scala.reflect.ClassTag

/** Interface implmented by wrappers to provide access to the underlying wrapped value. */
Expand Down Expand Up @@ -461,6 +462,29 @@ object CHeader {
* @see [[CostModel]] for detailed descriptions
*/
class CCostModel extends CostModel {
import CCostModel._

override def AccessBox: Int = AccessBoxCost

override def AccessAvlTree: Int = AccessAvlTreeCost

override def GetVar: Int = GetVarCost

def DeserializeVar: Int = DeserializeVarCost

def GetRegister: Int = GetRegisterCost

def DeserializeRegister: Int = DeserializeRegisterCost

def SelectField: Int = SelectFieldCost

def CollectionConst: Int = CollectionConstCost

def AccessKiloByteOfData: Int = AccessKiloByteOfDataCost

def PubKeySize: Long = CryptoConstants.EncodedGroupElementLength
}
object CCostModel {
private def costOf(opName: String, opType: SFunc): Int = {
val operId = OperationId(opName, opType)
costOf(operId)
Expand All @@ -471,28 +495,36 @@ class CCostModel extends CostModel {
cost
}

def AccessBox: Int = costOf("AccessBox", SFunc(SContext, SBox))
// NOTE: lazy vals are necessary to avoid initialization exception

def AccessAvlTree: Int = costOf("AccessAvlTree", SFunc(SContext, SAvlTree))
private val AccessBoxOpType: SFunc = SFunc(SContext, SBox)
private lazy val AccessBoxCost: Int = costOf("AccessBox", AccessBoxOpType)

def GetVar: Int = costOf("GetVar", SFunc(IndexedSeq(SContext, SByte), SOption(SOption.tT)))
private val AccessAvlTreeOpType: SFunc = SFunc(SContext, SAvlTree)
private lazy val AccessAvlTreeCost: Int = costOf("AccessAvlTree", AccessAvlTreeOpType)

def DeserializeVar: Int = costOf("DeserializeVar", SFunc(IndexedSeq(SContext, SByte), SOption(SOption.tT)))
private val GetVarOpType: SFunc = SFunc(Array(SContext, SByte), SOption.ThisType)
private lazy val GetVarCost: Int = costOf("GetVar", GetVarOpType)

def GetRegister: Int = costOf("GetRegister", SFunc(IndexedSeq(SBox, SByte), SOption(SOption.tT)))
private val DeserializeVarOpType: SFunc = SFunc(Array(SContext, SByte), SOption.ThisType)
private lazy val DeserializeVarCost: Int = costOf("DeserializeVar", DeserializeVarOpType)

def DeserializeRegister: Int = costOf("DeserializeRegister", SFunc(IndexedSeq(SBox, SByte), SOption(SOption.tT)))
private val GetRegisterOpType: SFunc = SFunc(Array(SBox, SByte), SOption.ThisType)
private lazy val GetRegisterCost: Int = costOf("GetRegister", GetRegisterOpType)

def SelectField: Int = costOf("SelectField", SFunc(IndexedSeq(), SUnit))
private val DeserializeRegisterOpType: SFunc = SFunc(Array(SBox, SByte), SOption.ThisType)
private lazy val DeserializeRegisterCost: Int = costOf("DeserializeRegister", DeserializeRegisterOpType)

def CollectionConst: Int = costOf("Const", SFunc(IndexedSeq(), SCollection(STypeVar("IV"))))
private val SelectFieldOpType: SFunc = SFunc(mutable.WrappedArray.empty, SUnit)
private lazy val SelectFieldCost: Int = costOf("SelectField", SelectFieldOpType)

def AccessKiloByteOfData: Int = costOf("AccessKiloByteOfData", SFunc(IndexedSeq(), SUnit))
private val CollectionConstOpType: SFunc = SFunc(mutable.WrappedArray.empty, SCollection.ThisType)
private lazy val CollectionConstCost: Int = costOf("Const", CollectionConstOpType)

def PubKeySize: Long = CryptoConstants.EncodedGroupElementLength
private val AccessKiloByteOfDataOpType: SFunc = SFunc(mutable.WrappedArray.empty, SUnit)
private lazy val AccessKiloByteOfDataCost: Int = costOf("AccessKiloByteOfData", AccessKiloByteOfDataOpType)
}


/** A default implementation of [[SigmaDslBuilder]] interface.
* @see [[SigmaDslBuilder]] for detailed descriptions
*/
Expand Down
2 changes: 1 addition & 1 deletion sigmastate/src/main/scala/sigmastate/eval/Evaluation.scala
Original file line number Diff line number Diff line change
Expand Up @@ -913,7 +913,7 @@ object Evaluation {
case PreHeaderRType => SPreHeader
case SigmaPropRType => SSigmaProp
case SigmaBooleanRType => SSigmaProp
case tup: TupleType => STuple(tup.items.map(t => rtypeToSType(t)).toIndexedSeq)
case tup: TupleType => STuple(tup.items.map(t => rtypeToSType(t)))
case at: ArrayType[_] => SCollection(rtypeToSType(at.tA))
case ct: CollType[_] => SCollection(rtypeToSType(ct.tItem))
case ft: FuncType[_,_] => SFunc(rtypeToSType(ft.tDom), rtypeToSType(ft.tRange))
Expand Down
13 changes: 12 additions & 1 deletion sigmastate/src/main/scala/sigmastate/eval/RuntimeCosting.scala
Original file line number Diff line number Diff line change
Expand Up @@ -1119,6 +1119,17 @@ trait RuntimeCosting extends CostingRules { IR: IRContext =>

var ruleStack: List[CostingRuleStat] = Nil

/** Recursively translates each ErgoTree `node` into the corresponding cost formula.
* The cost formula is represented using graph-based IR defined by this IRContext cake.
* Each `node: Value[T]` which evaluates to the value of type `T` is transformed
* to a value of type `RCosted[A]` which is a synonym of `Ref[Costed[A]]` type.
* The translation is performed recursively on a structure of the ErgoTree expression.
*
* @param ctx reference to the graph node, which represents costed `CONTEXT` expression.
* @param env environment of costed ValDef nodes (see BlockValue case).
* @param node expression to be costed
* @return a reference to the graph node of type Costed[T#WrappedType]`
*/
protected def evalNode[T <: SType](ctx: RCosted[Context], env: CostingEnv, node: Value[T]): RCosted[T#WrappedType] = {
import WOption._
def eval[T <: SType](node: Value[T]): RCosted[T#WrappedType] = evalNode(ctx, env, node)
Expand Down Expand Up @@ -1687,7 +1698,7 @@ trait RuntimeCosting extends CostingRules { IR: IRContext =>
def tC = evalNode(ctx, env, t)
def eC = evalNode(ctx, env, e)
val resV = IF (cC.value) THEN tC.value ELSE eC.value
val resCost = opCost(resV, Array(cC.cost, tC.cost, eC.cost), costOf("If", SFunc(Vector(SBoolean, If.tT, If.tT), If.tT)))
val resCost = opCost(resV, Array(cC.cost, tC.cost, eC.cost), costOf("If", If.GenericOpType))
RCCostedPrim(resV, resCost, tC.size) // TODO costing: implement tC.size max eC.size

case rel: Relation[t, _] =>
Expand Down
Loading

0 comments on commit 6c691d1

Please sign in to comment.