Skip to content

Commit

Permalink
Merge pull request #693 from ScorexFoundation/more-opts
Browse files Browse the repository at this point in the history
More memory and performance optimizations
  • Loading branch information
aslesarenko authored Nov 2, 2020
2 parents 3b0d77c + b8dd611 commit 7eafd40
Show file tree
Hide file tree
Showing 54 changed files with 823 additions and 571 deletions.
16 changes: 15 additions & 1 deletion common/src/main/scala/scalan/package.scala
Original file line number Diff line number Diff line change
Expand Up @@ -5,5 +5,19 @@ package object scalan {
/** Allows implicit resolution to find appropriate instance of ClassTag in
* the scope where RType is implicitly available. */
implicit def rtypeToClassTag[A](implicit t: RType[A]): ClassTag[A] = t.classTag


/** Immutable empty array of integers, should be used instead of allocating new empty arrays. */
val EmptyArrayOfInt = Array.empty[Int]

/** Immutable empty Seq[Int] backed by empty array.
* You should prefer using it instead of `Seq[Int]()` or `Seq.empty[Int]`
*/
val EmptySeqOfInt: Seq[Int] = EmptyArrayOfInt

/** Create a new empty buffer around pre-allocated empty array.
* This method is preferred, rather that creating empty debox.Buffer directly
* because it allows to avoid allocation of the empty array.
*/
def emptyDBufferOfInt: debox.Buffer[Int] = debox.Buffer.unsafe(EmptyArrayOfInt)

}
1 change: 1 addition & 0 deletions common/src/main/scala/scalan/util/CollectionUtil.scala
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,7 @@ object CollectionUtil {
res.toMap
}

// TODO optimize: using cfor and avoiding allocations
def joinSeqs[O, I, K](outer: GenIterable[O], inner: GenIterable[I])(outKey: O=>K, inKey: I=>K): GenIterable[(O,I)] = {
val kvs = createMultiMap(inner.map(i => (inKey(i), i)))
val res = outer.flatMap(o => {
Expand Down
49 changes: 37 additions & 12 deletions core/src/main/scala/scalan/Base.scala
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import scalan.compilation.GraphVizConfig
import scalan.util.StringUtil
import debox.{Buffer => DBuffer}
import spire.syntax.all.cfor
import scala.collection.mutable

/**
* The Base trait houses common AST nodes. It also manages a list of encountered definitions which
Expand Down Expand Up @@ -92,7 +93,7 @@ abstract class Base { scalan: Scalan =>
_elements(i + 1) = element
Def.extractSyms(element, symsBuf)
}
_syms = symsBuf.toArray()
_syms = if (symsBuf.length > 0) symsBuf.toArray() else EmptyArrayOfSym
}

/** References to other nodes in this Def instance.
Expand Down Expand Up @@ -236,7 +237,7 @@ abstract class Base { scalan: Scalan =>
override def mirror(t: Transformer): Ref[T] = self
}

/** Describes lifting data values of type ST (Source Type) to IR nodes of the correspoding staged type T.
/** Describes lifting data values of type ST (Source Type) to IR nodes of the corresponding staged type T.
* In general T is different type obtained by virtualization procedure from ST.
* However ST can be the same as T as is the case for Byte, Int, String etc.
*/
Expand Down Expand Up @@ -452,22 +453,28 @@ abstract class Base { scalan: Scalan =>
/** Transform a sequence of nodes into new sequence of nodes. */
final def apply[A](xs: Seq[Ref[A]]): Seq[Ref[A]] = {
val len = xs.length
val res = new Array[Ref[A]](len)
cfor(0)(_ < len, _ + 1) { i =>
res(i) = apply(xs(i))
if (len == 0) EmptySeqOfSym.asInstanceOf[Seq[Ref[A]]]
else {
val res = new Array[Ref[A]](len)
cfor(0)(_ < len, _ + 1) { i =>
res(i) = apply(xs(i))
}
res
}
res
}
/** Apply this transformer to the nodes present in the sequence,
* and leave non-Ref items unchanged. */
final def apply(xs: Seq[Any])(implicit o: Overloaded1): Seq[Any] = {
val len = xs.length
val res = new Array[Any](len)
cfor(0)(_ < len, _ + 1) { i =>
val x = xs(i) match { case s: Ref[_] => apply(s); case s => s }
res(i) = x
if (len == 0) mutable.WrappedArray.empty
else {
val res = new Array[Any](len)
cfor(0)(_ < len, _ + 1) { i =>
val x = xs(i) match { case s: Ref[_] => apply(s); case s => s }
res(i) = x
}
res
}
res
}

def +[A](key: Sym, value: Sym): Transformer
Expand Down Expand Up @@ -815,5 +822,23 @@ abstract class Base { scalan: Scalan =>
} while (res != currSym)
res
}
}

/** Immutable empty array of symbols, can be used to avoid unnecessary allocations. */
val EmptyArrayOfSym = Array.empty[Sym]

/** Immutable empty Seq, can be used to avoid unnecessary allocations. */
val EmptySeqOfSym: Seq[Sym] = EmptyArrayOfSym

/** Create a new empty buffer around pre-allocated empty array.
* This method is preferred, rather that creating empty debox.Buffer directly
* because it allows to avoid allocation of the empty array.
*/
@inline final def emptyDBufferOfSym: DBuffer[Sym] = DBuffer.unsafe(EmptyArrayOfSym)

/** Used internally in IR and should be used with care since it is mutable.
* At the same time, it is used in the hotspot and allows to avoid roughly tens of
* thousands of allocations per second.
* WARNING: Mutations of this instance can lead to undefined behavior.
*/
protected val EmptyDSetOfInt: debox.Set[Int] = debox.Set.empty
}
4 changes: 1 addition & 3 deletions core/src/main/scala/scalan/Scalan.scala
Original file line number Diff line number Diff line change
@@ -1,8 +1,7 @@
package scalan

import scalan.compilation.GraphVizExport
import scalan.primitives._
import scalan.staged.{Transforming}
import scalan.staged.Transforming

/** Aggregate cake with all inter-dependent modules assembled together.
* Each instance of this class contains independent IR context, thus many
Expand Down Expand Up @@ -30,7 +29,6 @@ class Scalan
with Functions
with IfThenElse
with Transforming
// with GraphVizExport
with Thunks
with Entities
with Modules
Expand Down
5 changes: 3 additions & 2 deletions core/src/main/scala/scalan/TypeDescs.scala
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,7 @@ abstract class TypeDescs extends Base { self: Scalan =>
// }
// }

// TODO optimize performance hot spot (45% of invokeUnlifted time)
// TODO optimize performance hot spot (45% of invokeUnlifted time), reduce allocation of Some
final def getSourceValues(dataEnv: DataEnv, forWrapper: Boolean, stagedValues: AnyRef*): Seq[AnyRef] = {
import OverloadHack._
val limit = stagedValues.length
Expand Down Expand Up @@ -151,7 +151,7 @@ abstract class TypeDescs extends Base { self: Scalan =>
!!!(s"Cannot get Liftable instance for $this")

final lazy val sourceType: RType[_] = liftable.sourceType
protected def collectMethods: Map[Method, MethodDesc] = Map()
protected def collectMethods: Map[Method, MethodDesc] = Map() // TODO optimize: all implementations
protected lazy val methods: Map[Method, MethodDesc] = collectMethods

// TODO benchamrk against the version below it
Expand Down Expand Up @@ -244,6 +244,7 @@ abstract class TypeDescs extends Base { self: Scalan =>
m.getName
}

// TODO optimize
/** Build a mapping between methods of staged class and the corresponding methods of source class.
* The methods are related using names.
* The computed mapping can be used to project MethodCalls IR nodes back to the corresponding
Expand Down
8 changes: 6 additions & 2 deletions core/src/main/scala/scalan/primitives/Equal.scala
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,14 @@ import scalan.{Base, Scalan}

trait Equal extends Base { self: Scalan =>
/** Binary operation representing structural equality between arguments. */
case class Equals[A: Elem]() extends BinOp[A, Boolean]("==", equalValues[A](_, _))
case class Equals[A: Elem]() extends BinOp[A, Boolean]("==") {
override def applySeq(x: A, y: A): Boolean = equalValues[A](x, y)
}

/** Binary operation representing structural inequality between arguments. */
case class NotEquals[A: Elem]() extends BinOp[A, Boolean]("!=", !equalValues[A](_, _))
case class NotEquals[A: Elem]() extends BinOp[A, Boolean]("!=") {
override def applySeq(x: A, y: A): Boolean = !equalValues[A](x, y)
}

protected def equalValues[A](x: Any, y: Any)(implicit eA: Elem[A]) = x == y

Expand Down
13 changes: 2 additions & 11 deletions core/src/main/scala/scalan/primitives/Functions.scala
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import java.util

import scalan.staged.ProgramGraphs
import scalan.util.GraphUtil
import scalan.{Lazy, Base, Nullable, Scalan}
import scalan.{Nullable, emptyDBufferOfInt, Base, Lazy, Scalan}
import debox.{Buffer => DBuffer}

import scala.language.implicitConversions
Expand Down Expand Up @@ -120,7 +120,7 @@ trait Functions extends Base with ProgramGraphs { self: Scalan =>

override lazy val scheduleIds: DBuffer[Int] = {
val sch = if (isIdentity)
DBuffer.ofSize[Int](0)
emptyDBufferOfInt
else {
// graph g will contain all Defs reified as part of this Lambda, (due to `filterNode`)
// BUT not all of them depend on boundVars, thus we need to filter them out
Expand Down Expand Up @@ -165,15 +165,6 @@ trait Functions extends Base with ProgramGraphs { self: Scalan =>
}

override protected def getDeps: Array[Sym] = freeVars.toArray

def isGlobalLambda: Boolean = {
freeVars.forall { x =>
x.isConst || {
val xIsGlobalLambda = x.isLambda && { val lam = x.node.asInstanceOf[Lambda[_, _]]; lam.isGlobalLambda }
xIsGlobalLambda
}
}
}
}

type LambdaData[A,B] = (Lambda[A,B], Nullable[Ref[A] => Ref[B]], Ref[A], Ref[B])
Expand Down
28 changes: 23 additions & 5 deletions core/src/main/scala/scalan/primitives/LogicalOps.scala
Original file line number Diff line number Diff line change
Expand Up @@ -2,17 +2,34 @@ package scalan.primitives

import scalan.{Base, Scalan}

/** Slice in Scala cake with definitions of logical operations. */
trait LogicalOps extends Base { self: Scalan =>
val And = new EndoBinOp[Boolean]("&&", _ && _)
/** Logical AND binary operation. */
val And = new EndoBinOp[Boolean]("&&") {
override def applySeq(x: Boolean, y: Boolean): Boolean = x && y
}

val Or = new EndoBinOp[Boolean]("||", _ || _)
/** Logical AND binary operation. */
val Or = new EndoBinOp[Boolean]("||") {
override def applySeq(x: Boolean, y: Boolean): Boolean = x || y
}

val Not = new EndoUnOp[Boolean]("!", !_)
/** Logical NOT unary operation. */
val Not = new EndoUnOp[Boolean]("!") {
override def applySeq(x: Boolean): Boolean = !x
}

val BinaryXorOp = new EndoBinOp[Boolean]("^", _ ^ _)
/** Logical XOR binary operation. */
val BinaryXorOp = new EndoBinOp[Boolean]("^") {
override def applySeq(x: Boolean, y: Boolean): Boolean = x ^ y
}

val BooleanToInt = new UnOp[Boolean, Int]("ToInt", if (_) 1 else 0)
/** Boolean to Int conversion unary operation. */
val BooleanToInt = new UnOp[Boolean, Int]("ToInt") {
override def applySeq(x: Boolean): Int = if (x) 1 else 0
}

/** Extension methods over `Ref[Boolean]`. */
implicit class RepBooleanOps(value: Ref[Boolean]) {
def &&(y: Ref[Boolean]): Ref[Boolean] = And(value, y)
def ||(y: Ref[Boolean]): Ref[Boolean] = Or(value, y)
Expand All @@ -26,6 +43,7 @@ trait LogicalOps extends Base { self: Scalan =>
}


/** Helper method which defines rewriting rules with boolean constants. */
@inline
final def rewriteBoolConsts(lhs: Sym, rhs: Sym, ifTrue: Sym => Sym, ifFalse: Sym => Sym, ifEqual: Sym => Sym, ifNegated: Sym => Sym): Sym =
lhs match {
Expand Down
73 changes: 61 additions & 12 deletions core/src/main/scala/scalan/primitives/NumericOps.scala
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,10 @@ package scalan.primitives

import scalan.{ExactNumeric, Base, Scalan, ExactIntegral}

/** Slice in Scala cake with definitions of numeric operations. */
trait NumericOps extends Base { self: Scalan =>

/** Extension methods over `Ref[T]` where T is instance of ExactNumeric type-class. */
implicit class NumericOpsCls[T](x: Ref[T])(implicit val n: ExactNumeric[T]) {
def +(y: Ref[T]): Ref[T] = NumericPlus(n)(x.elem).apply(x, y)
def -(y: Ref[T]): Ref[T] = NumericMinus(n)(x.elem).apply(x, y)
Expand All @@ -15,6 +18,7 @@ trait NumericOps extends Base { self: Scalan =>
def toLong: Ref[Long] = NumericToLong(n).apply(x)
}

/** Extension methods over `Ref[T]` where T is instance of ExactIntegral type-class. */
implicit class IntegralOpsCls[T](x: Ref[T])(implicit i: ExactIntegral[T]) {
def div(y: Ref[T]): Ref[T] = IntegralDivide(i)(x.elem).apply(x, y)
def mod(y: Ref[T]): Ref[T] = IntegralMod(i)(x.elem).apply(x, y)
Expand All @@ -23,35 +27,80 @@ trait NumericOps extends Base { self: Scalan =>
def %(y: Ref[T]): Ref[T] = mod(y)
}

/** Return an ExactNumeric for a given type T. */
def numeric[T:ExactNumeric]: ExactNumeric[T] = implicitly[ExactNumeric[T]]

/** Return an ExactIntegral for a given type T. */
def integral[T:ExactIntegral]: ExactIntegral[T] = implicitly[ExactIntegral[T]]

case class NumericPlus[T: Elem](n: ExactNumeric[T]) extends EndoBinOp[T]("+", n.plus)
/** Descriptor of binary `+` operation. */
case class NumericPlus[T: Elem](n: ExactNumeric[T]) extends EndoBinOp[T]("+") {
override def applySeq(x: T, y: T): T = n.plus(x, y)
}

case class NumericMinus[T: Elem](n: ExactNumeric[T]) extends EndoBinOp[T]("-", n.minus)
/** Descriptor of binary `-` operation. */
case class NumericMinus[T: Elem](n: ExactNumeric[T]) extends EndoBinOp[T]("-") {
override def applySeq(x: T, y: T): T = n.minus(x, y)
}

case class NumericTimes[T: Elem](n: ExactNumeric[T]) extends EndoBinOp[T]("*", n.times)
/** Descriptor of binary `*` operation. */
case class NumericTimes[T: Elem](n: ExactNumeric[T]) extends EndoBinOp[T]("*") {
override def applySeq(x: T, y: T): T = n.times(x, y)
}

class DivOp[T: Elem](opName: String, applySeq: (T, T) => T, n: ExactIntegral[T]) extends EndoBinOp[T](opName, applySeq) {
/** Base class for descriptors of binary division operations. */
abstract class DivOp[T: Elem](opName: String, n: ExactIntegral[T]) extends EndoBinOp[T](opName) {
override def shouldPropagate(lhs: T, rhs: T) = rhs != n.zero
}

case class NumericNegate[T: Elem](n: ExactNumeric[T]) extends UnOp[T, T]("-", n.negate)
/** Descriptor of unary `-` operation. */
case class NumericNegate[T: Elem](n: ExactNumeric[T]) extends UnOp[T, T]("-") {
override def applySeq(x: T): T = n.negate(x)
}

case class NumericToDouble[T](n: ExactNumeric[T]) extends UnOp[T,Double]("ToDouble", n.toDouble)
/** Descriptor of unary `ToDouble` conversion operation. */
case class NumericToDouble[T](n: ExactNumeric[T]) extends UnOp[T,Double]("ToDouble") {
override def applySeq(x: T): Double = n.toDouble(x)
}

case class NumericToFloat[T](n: ExactNumeric[T]) extends UnOp[T, Float]("ToFloat", n.toFloat)
/** Descriptor of unary `ToFloat` conversion operation. */
case class NumericToFloat[T](n: ExactNumeric[T]) extends UnOp[T, Float]("ToFloat") {
override def applySeq(x: T): Float = n.toFloat(x)
}

case class NumericToInt[T](n: ExactNumeric[T]) extends UnOp[T,Int]("ToInt", n.toInt)
/** Descriptor of unary `ToInt` conversion operation. */
case class NumericToInt[T](n: ExactNumeric[T]) extends UnOp[T,Int]("ToInt") {
override def applySeq(x: T): Int = n.toInt(x)
}

case class NumericToLong[T](n: ExactNumeric[T]) extends UnOp[T,Long]("ToLong", n.toLong)
/** Descriptor of unary `ToLong` conversion operation. */
case class NumericToLong[T](n: ExactNumeric[T]) extends UnOp[T,Long]("ToLong") {
override def applySeq(x: T): Long = n.toLong(x)
}

case class Abs[T: Elem](n: ExactNumeric[T]) extends UnOp[T, T]("Abs", n.abs)
/** Descriptor of unary `abs` operation. */
case class Abs[T: Elem](n: ExactNumeric[T]) extends UnOp[T, T]("Abs") {
override def applySeq(x: T): T = n.abs(x)
}

case class IntegralDivide[T](i: ExactIntegral[T])(implicit elem: Elem[T]) extends DivOp[T]("/", i.quot, i)
/** Descriptor of binary `/` operation (integral division). */
case class IntegralDivide[T](i: ExactIntegral[T])(implicit elem: Elem[T]) extends DivOp[T]("/", i) {
override def applySeq(x: T, y: T): T = i.quot(x, y)
}

case class IntegralMod[T](i: ExactIntegral[T])(implicit elem: Elem[T]) extends DivOp[T]("%", i.rem, i)
/** Descriptor of binary `%` operation (reminder of integral division). */
case class IntegralMod[T](i: ExactIntegral[T])(implicit elem: Elem[T]) extends DivOp[T]("%", i) {
/** Note, this is implemented using `ExactIntegral.rem` method which delegates to
* `scala.math.Integral.rem`. The later also implements `%` operator in Scala for
* numeric types.
* @see sigmastate.eval.NumericOps.BigIntIsIntegral
*/
override def applySeq(x: T, y: T): T = i.rem(x, y)
}

/** Compares the given value with zero of the given ExactNumeric instance. */
@inline final def isZero[T](x: T, n: ExactNumeric[T]) = x == n.zero

/** Compares the given value with 1 of the given ExactNumeric instance. */
@inline final def isOne[T](x: T, n: ExactNumeric[T]) = x == n.fromInt(1)
}
Loading

0 comments on commit 7eafd40

Please sign in to comment.