Skip to content

Commit

Permalink
NODE-2612 RIDE replaceByIndex() (#3881)
Browse files Browse the repository at this point in the history
  • Loading branch information
xrtm000 authored Sep 28, 2023
1 parent 65646ef commit 3c40276
Show file tree
Hide file tree
Showing 8 changed files with 238 additions and 64 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
package com.wavesplatform.lang.v1

import com.wavesplatform.common.utils.EitherExt2
import com.wavesplatform.lang.Common
import com.wavesplatform.lang.directives.values.StdLibVersion
import com.wavesplatform.lang.v1.FunctionHeader.Native
import com.wavesplatform.lang.v1.compiler.Terms.*
import com.wavesplatform.lang.v1.evaluator.FunctionIds.REPLACE_BY_INDEX_OF_LIST
import com.wavesplatform.lang.v1.evaluator.ctx.impl.PureContext
import com.wavesplatform.lang.v1.traits.Environment
import org.openjdk.jmh.annotations.*
import org.openjdk.jmh.infra.Blackhole

import scala.concurrent.duration.{MICROSECONDS, SECONDS}

@OutputTimeUnit(MICROSECONDS)
@BenchmarkMode(Array(Mode.AverageTime))
@Threads(1)
@Fork(1)
@Warmup(iterations = 10, time = 1, timeUnit = SECONDS)
@Measurement(iterations = 10, time = 1, timeUnit = SECONDS)
class ListReplaceByIndexBenchmark {
@Benchmark
def listReplaceFirstByIndex(st: ListReplaceByIndexSt, bh: Blackhole): Unit =
bh.consume(eval(st.ctx, st.replaceFirst))

@Benchmark
def listReplaceMiddleByIndex(st: ListReplaceByIndexSt, bh: Blackhole): Unit =
bh.consume(eval(st.ctx, st.replaceMiddle))

@Benchmark
def listReplaceLastByIndex(st: ListReplaceByIndexSt, bh: Blackhole): Unit =
bh.consume(eval(st.ctx, st.replaceLast))
}

@State(Scope.Benchmark)
class ListReplaceByIndexSt {
val ctx =
PureContext
.build(StdLibVersion.VersionDic.all.max, useNewPowPrecision = true)
.withEnvironment[Environment]
.evaluationContext(Common.emptyBlockchainEnvironment())

val list = ARR(Vector.fill(1000)(CONST_LONG(Long.MaxValue)), limited = true).explicitGet()

val replaceFirst =
FUNCTION_CALL(
Native(REPLACE_BY_INDEX_OF_LIST),
List(
list,
CONST_LONG(0),
CONST_LONG(777)
)
)

val replaceMiddle =
FUNCTION_CALL(
Native(REPLACE_BY_INDEX_OF_LIST),
List(
list,
CONST_LONG(500),
CONST_LONG(777)
)
)

val replaceLast =
FUNCTION_CALL(
Native(REPLACE_BY_INDEX_OF_LIST),
List(
list,
CONST_LONG(999),
CONST_LONG(777)
)
)
}
Original file line number Diff line number Diff line change
@@ -1,100 +1,97 @@
package com.wavesplatform.lang.v1

import java.util.concurrent.TimeUnit

import cats.Id
import cats.kernel.Monoid
import cats.implicits.catsSyntaxSemigroup
import com.wavesplatform.common.state.ByteStr
import com.wavesplatform.common.utils.{Base58, EitherExt2}
import com.wavesplatform.crypto.Curve25519
import com.wavesplatform.lang.Global
import com.wavesplatform.lang.directives.values.{V1, V4}
import com.wavesplatform.lang.directives.values.StdLibVersion
import com.wavesplatform.lang.v1.EnvironmentFunctionsBenchmark.{curve25519, randomBytes}
import com.wavesplatform.lang.v1.FunctionHeader.Native
import com.wavesplatform.lang.v1.ScriptEvaluatorBenchmark.*
import com.wavesplatform.lang.v1.compiler.Terms.*
import com.wavesplatform.lang.v1.evaluator.Contextful.NoContext
import com.wavesplatform.lang.v1.evaluator.EvaluatorV1.*
import com.wavesplatform.lang.v1.evaluator.FunctionIds
import com.wavesplatform.lang.v1.evaluator.FunctionIds.{FROMBASE58, SIGVERIFY, TOBASE58}
import com.wavesplatform.lang.v1.evaluator.ctx.EvaluationContext
import com.wavesplatform.lang.v1.evaluator.ctx.impl.{CryptoContext, PureContext}
import com.wavesplatform.lang.v1.evaluator.{EvaluatorV1, FunctionIds}
import com.wavesplatform.lang.v1.traits.Environment
import com.wavesplatform.lang.{Common, Global}
import org.openjdk.jmh.annotations.*
import org.openjdk.jmh.infra.Blackhole

import java.util.concurrent.TimeUnit
import scala.concurrent.duration.SECONDS
import scala.util.Random

object ScriptEvaluatorBenchmark {
val version = V1
val pureEvalContext: EvaluationContext[NoContext, Id] =
PureContext.build(V1, useNewPowPrecision = true).evaluationContext
val evaluatorV1: EvaluatorV1[Id, NoContext] = new EvaluatorV1[Id, NoContext]()
val lastVersion = StdLibVersion.VersionDic.all.max
val context =
(PureContext.build(lastVersion, useNewPowPrecision = true) |+| CryptoContext.build(Global, lastVersion))
.withEnvironment[Environment]
.evaluationContext(Common.emptyBlockchainEnvironment())
}

@OutputTimeUnit(TimeUnit.MICROSECONDS)
@BenchmarkMode(Array(Mode.AverageTime))
@Threads(1)
@Fork(1)
@Warmup(iterations = 10)
@Measurement(iterations = 10)
@Warmup(iterations = 10, time = 1, timeUnit = SECONDS)
@Measurement(iterations = 10, time = 1, timeUnit = SECONDS)
class ScriptEvaluatorBenchmark {
@Benchmark
def bigSum(st: BigSum, bh: Blackhole): Unit = bh.consume(evaluatorV1.apply[EVALUATED](pureEvalContext, st.expr))
def bigSum(st: BigSum, bh: Blackhole): Unit = bh.consume(eval(context, st.expr))

@Benchmark
def nestedBlocks(st: NestedBlocks, bh: Blackhole): Unit = bh.consume(evaluatorV1.apply[EVALUATED](st.context, st.expr))
def nestedBlocks(st: NestedBlocks, bh: Blackhole): Unit = bh.consume(eval(context, st.expr))

@Benchmark
def signatures(st: Signatures, bh: Blackhole): Unit = bh.consume(evaluatorV1.apply[EVALUATED](st.context, st.expr))
def signatures(st: Signatures, bh: Blackhole): Unit = bh.consume(eval(context, st.expr))

@Benchmark
def base58encode(st: Base58Perf, bh: Blackhole): Unit = bh.consume(evaluatorV1.apply[EVALUATED](st.context, st.encode))
def base58encode(st: Base58Perf, bh: Blackhole): Unit = bh.consume(eval(context, st.encode))

@Benchmark
def base58decode(st: Base58Perf, bh: Blackhole): Unit = bh.consume(evaluatorV1.apply[EVALUATED](st.context, st.decode))
def base58decode(st: Base58Perf, bh: Blackhole): Unit = bh.consume(eval(context, st.decode))

@Benchmark
def stringConcat(st: Concat, bh: Blackhole): Unit = bh.consume(evaluatorV1.apply[EVALUATED](st.context, st.strings))
def stringConcat(st: Concat, bh: Blackhole): Unit = bh.consume(eval(context, st.strings))

@Benchmark
def bytesConcat(st: Concat, bh: Blackhole): Unit = bh.consume(evaluatorV1.apply[EVALUATED](st.context, st.bytes))
def bytesConcat(st: Concat, bh: Blackhole): Unit = bh.consume(eval(context, st.bytes))

@Benchmark
def listMedianRandomElements(st: Median, bh: Blackhole): Unit =
bh.consume(evaluatorV1.apply[EVALUATED](st.context, st.randomElements(Random.nextInt(10000))))
bh.consume(eval(context, st.randomElements(Random.nextInt(10000))))

@Benchmark
def listMedianSortedElements(st: Median, bh: Blackhole): Unit =
bh.consume(evaluatorV1.apply[EVALUATED](st.context, st.sortedElements))
bh.consume(eval(context, st.sortedElements))

@Benchmark
def listMedianSortedReverseElements(st: Median, bh: Blackhole): Unit =
bh.consume(evaluatorV1.apply[EVALUATED](st.context, st.sortedReverseElements))
bh.consume(eval(context, st.sortedReverseElements))

@Benchmark
def listMedianEqualElements(st: Median, bh: Blackhole): Unit =
bh.consume(evaluatorV1.apply[EVALUATED](st.context, st.equalElements))
bh.consume(eval(context, st.equalElements))

@Benchmark
def listRemoveFirstByIndex(st: ListRemoveByIndex, bh: Blackhole): Unit =
bh.consume(evaluatorV1.apply[EVALUATED](st.context, st.removeFirst))
bh.consume(eval(context, st.removeFirst))

@Benchmark
def listRemoveMiddleByIndex(st: ListRemoveByIndex, bh: Blackhole): Unit =
bh.consume(evaluatorV1.apply[EVALUATED](st.context, st.removeMiddle))
bh.consume(eval(context, st.removeMiddle))

@Benchmark
def listRemoveLastByIndex(st: ListRemoveByIndex, bh: Blackhole): Unit =
bh.consume(evaluatorV1.apply[EVALUATED](st.context, st.removeLast))
bh.consume(eval(context, st.removeLast))

@Benchmark
def sigVerify32Kb(st: SigVerify32Kb, bh: Blackhole): Unit = bh.consume(evaluatorV1.apply[EVALUATED](st.context, st.expr))
def sigVerify32Kb(st: SigVerify32Kb, bh: Blackhole): Unit =
bh.consume(eval(context, st.expr))
}

@State(Scope.Benchmark)
class NestedBlocks {
val context: EvaluationContext[NoContext, Id] = pureEvalContext

val expr: EXPR = {
val blockCount = 300
val cond = FUNCTION_CALL(PureContext.eq, List(REF(s"v$blockCount"), CONST_LONG(0)))
Expand All @@ -107,9 +104,6 @@ class NestedBlocks {

@State(Scope.Benchmark)
class Base58Perf {
val context: EvaluationContext[NoContext, Id] =
Monoid.combine(pureEvalContext, CryptoContext.build(Global, version).evaluationContext)

val encode: EXPR = {
val base58Count = 120
val sum = (1 to base58Count).foldRight[EXPR](CONST_LONG(0)) { case (i, e) =>
Expand Down Expand Up @@ -150,9 +144,6 @@ class Base58Perf {

@State(Scope.Benchmark)
class Signatures {
val context: EvaluationContext[NoContext, Id] =
Monoid.combine(pureEvalContext, CryptoContext.build(Global, version).evaluationContext)

val expr: EXPR = {
val sigCount = 20
val sum = (1 to sigCount).foldRight[EXPR](CONST_LONG(0)) { case (i, e) =>
Expand Down Expand Up @@ -191,8 +182,6 @@ class Signatures {

@State(Scope.Benchmark)
class Concat {
val context: EvaluationContext[NoContext, Id] = pureEvalContext

private val Steps = 180

private def expr(init: EXPR, func: FunctionHeader, operand: EXPR, count: Int) =
Expand All @@ -218,8 +207,6 @@ class Concat {

@State(Scope.Benchmark)
class Median {
val context: EvaluationContext[NoContext, Id] = PureContext.build(V4, useNewPowPrecision = true).evaluationContext

val randomElements: Array[EXPR] =
(1 to 10000).map { _ =>
val listOfLong = (1 to 1000).map(_ => CONST_LONG(Random.nextLong()))
Expand Down Expand Up @@ -260,9 +247,6 @@ class Median {

@State(Scope.Benchmark)
class SigVerify32Kb {
val context: EvaluationContext[NoContext, Id] =
Monoid.combine(PureContext.build(V4, useNewPowPrecision = true).evaluationContext, CryptoContext.build(Global, V4).evaluationContext)

val expr: EXPR = {
val (privateKey, publicKey) = curve25519.generateKeypair
val message = randomBytes(32 * 1024 - 1)
Expand All @@ -281,12 +265,6 @@ class SigVerify32Kb {

@State(Scope.Benchmark)
class ListRemoveByIndex {
val context: EvaluationContext[NoContext, Id] =
Monoid.combine(
PureContext.build(V4, useNewPowPrecision = true).evaluationContext,
CryptoContext.build(Global, V4).evaluationContext
)

val list: ARR = ARR(Vector.fill(1000)(CONST_LONG(Long.MaxValue)), limited = true).explicitGet()

val removeFirst: EXPR =
Expand All @@ -312,7 +290,7 @@ class ListRemoveByIndex {
Native(FunctionIds.REMOVE_BY_INDEX_OF_LIST),
List(
list,
CONST_LONG(1000)
CONST_LONG(999)
)
)
}
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ package object v1 {
def eval(
ctx: EvaluationContext[Environment, Id],
expr: EXPR,
stdLibVersion: StdLibVersion
stdLibVersion: StdLibVersion = StdLibVersion.VersionDic.all.max
): (Log[Id], Int, Either[ExecutionError, Terms.EVALUATED]) =
EvaluatorV2.applyCompleted(ctx, expr, LogExtraInfo(), stdLibVersion, newMode = true, correctFunctionCallScope = true, enableExecutionLog = false)
}
11 changes: 9 additions & 2 deletions lang/doc/v8/funcs/list-functions.hjson
Original file line number Diff line number Diff line change
Expand Up @@ -109,8 +109,15 @@
name: "removeByIndex"
params: [ "List[T]", "Int" ]
doc: "Removes the element at given index from the list and returns new list."
paramsDoc: [ "The list.", "The element." ]
complexity: 7
paramsDoc: [ "The list.", "The index." ]
complexity: 4
}
{
name: "replaceByIndex"
params: [ "List[T]", "Int", "T" ]
doc: "Replaces the element at given index in the list and returns new list."
paramsDoc: [ "The list.", "The index.", "New element." ]
complexity: 4
}
]
}
Original file line number Diff line number Diff line change
Expand Up @@ -71,12 +71,13 @@ object FunctionIds {
val STRING_TO_BIGINTOPT: Short = 424
val MEDIAN_LISTBIGINT: Short = 425

val CREATE_LIST: Short = 1100
val APPEND_LIST: Short = 1101
val CONCAT_LIST: Short = 1102
val INDEX_OF_LIST: Short = 1103
val LAST_INDEX_OF_LIST: Short = 1104
val REMOVE_BY_INDEX_OF_LIST: Short = 1105
val CREATE_LIST: Short = 1100
val APPEND_LIST: Short = 1101
val CONCAT_LIST: Short = 1102
val INDEX_OF_LIST: Short = 1103
val LAST_INDEX_OF_LIST: Short = 1104
val REMOVE_BY_INDEX_OF_LIST: Short = 1105
val REPLACE_BY_INDEX_OF_LIST: Short = 1106

val UTF8STRING: Short = 1200
val BININT: Short = 1201
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1452,7 +1452,7 @@ object PureContext {
lazy val listRemoveByIndex: BaseFunction[NoContext] =
NativeFunction(
"removeByIndex",
7,
Map(V4 -> 7L, V5 -> 7L, V6 -> 7L, V7 -> 7L, V8 -> 4L),
REMOVE_BY_INDEX_OF_LIST,
PARAMETERIZEDLIST(TYPEPARAM('T')),
("list", PARAMETERIZEDLIST(TYPEPARAM('T'))),
Expand All @@ -1471,6 +1471,29 @@ object PureContext {
notImplemented[Id, EVALUATED]("removeByIndex(list: List[T], index: Int)", xs)
}

private val listReplaceByIndex: BaseFunction[NoContext] =
NativeFunction(
"replaceByIndex",
4,
REPLACE_BY_INDEX_OF_LIST,
PARAMETERIZEDLIST(TYPEPARAM('T')),
("list", PARAMETERIZEDLIST(TYPEPARAM('T'))),
("index", LONG),
("element", TYPEPARAM('T'))
) {
case ARR(list) :: CONST_LONG(index) :: element :: Nil =>
if (list.isEmpty)
Left("Can't replace an element in empty list")
else if (index < 0)
Left(s"Index of the replacing element should be positive, but $index was passed")
else if (index >= list.size)
Left(s"Index of the replacing element should be lower than list size = ${list.length}, but $index was passed")
else
ARR(list.updated(index.toInt, element), limited = true)
case xs =>
notImplemented[Id, EVALUATED]("replaceByIndex(list: List[T], index: Int, element: T)", xs)
}

@VisibleForTesting
private[v1] def genericListIndexOf(
element: EVALUATED,
Expand Down Expand Up @@ -2041,12 +2064,20 @@ object PureContext {
v6Functions
)

private[this] val v8Ctx =
CTX[NoContext](
v5Types,
v5Vars,
v6Functions :+ listReplaceByIndex
)

def build(version: StdLibVersion, useNewPowPrecision: Boolean): CTX[NoContext] =
version match {
case V1 | V2 => v1V2Ctx(useNewPowPrecision)
case V3 => v3Ctx(useNewPowPrecision)
case V4 => v4Ctx(useNewPowPrecision)
case V5 => v5Ctx(useNewPowPrecision)
case _ => v6Ctx
case V6 | V7 => v6Ctx
case V8 => v8Ctx
}
}
Loading

0 comments on commit 3c40276

Please sign in to comment.