Optimize int range loops and add benchmark gating
This commit is contained in:
parent
74d73540c6
commit
98d6ffe998
@ -0,0 +1,28 @@
|
||||
/*
|
||||
* Copyright 2026 Sergey S. Chernov real.sergeych@gmail.com
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*
|
||||
*/
|
||||
|
||||
package net.sergeych.lyng
|
||||
|
||||
actual object Benchmarks {
|
||||
actual val enabled: Boolean = run {
|
||||
val p = System.getProperty("LYNG_BENCHMARKS")?.lowercase()
|
||||
val e = System.getenv("BENCHMARKS")?.lowercase()
|
||||
fun parse(v: String?): Boolean =
|
||||
v == "true" || v == "1" || v == "yes"
|
||||
parse(p) || parse(e)
|
||||
}
|
||||
}
|
||||
@ -254,4 +254,3 @@ data class ParsedArgument(
|
||||
fun from(values: Collection<Obj>) = Arguments(values.toList())
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -0,0 +1,22 @@
|
||||
/*
|
||||
* Copyright 2026 Sergey S. Chernov real.sergeych@gmail.com
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*
|
||||
*/
|
||||
|
||||
package net.sergeych.lyng
|
||||
|
||||
expect object Benchmarks {
|
||||
val enabled: Boolean
|
||||
}
|
||||
@ -413,11 +413,7 @@ class Compiler(
|
||||
private suspend fun parseExpression(): Statement? {
|
||||
val pos = cc.currentPos()
|
||||
return parseExpressionLevel()?.let { ref ->
|
||||
val stmtPos = pos
|
||||
object : Statement() {
|
||||
override val pos: Pos = stmtPos
|
||||
override suspend fun execute(scope: Scope): Obj = ref.evalValue(scope)
|
||||
}
|
||||
ExpressionStatement(ref, pos)
|
||||
}
|
||||
}
|
||||
|
||||
@ -722,11 +718,18 @@ class Compiler(
|
||||
null
|
||||
else
|
||||
parseExpression()
|
||||
operand = RangeRef(
|
||||
left,
|
||||
right?.let { StatementRef(it) },
|
||||
isEndInclusive
|
||||
)
|
||||
val rightRef = right?.let { StatementRef(it) }
|
||||
if (left != null && rightRef != null) {
|
||||
val lConst = constIntValueOrNull(left)
|
||||
val rConst = constIntValueOrNull(rightRef)
|
||||
if (lConst != null && rConst != null) {
|
||||
operand = ConstRef(ObjRange(ObjInt.of(lConst), ObjInt.of(rConst), isEndInclusive).asReadonly)
|
||||
} else {
|
||||
operand = RangeRef(left, rightRef, isEndInclusive)
|
||||
}
|
||||
} else {
|
||||
operand = RangeRef(left, rightRef, isEndInclusive)
|
||||
}
|
||||
}
|
||||
|
||||
Token.Type.LBRACE, Token.Type.NULL_COALESCE_BLOCKINVOKE -> {
|
||||
@ -2467,12 +2470,19 @@ class Compiler(
|
||||
// So we parse an expression explicitly and wrap it into a StatementRef.
|
||||
val exprAfterIn = parseExpression() ?: throw ScriptError(start, "Bad for statement: expected expression")
|
||||
val source: Statement = exprAfterIn
|
||||
val constRange = (exprAfterIn as? ExpressionStatement)?.ref?.let { ref ->
|
||||
constIntRangeOrNull(ref)
|
||||
}
|
||||
ensureRparen()
|
||||
|
||||
// Expose the loop variable name to the parser so identifiers inside the loop body
|
||||
// can be emitted as FastLocalVarRef when enabled.
|
||||
val namesForLoop = (currentLocalNames?.toSet() ?: emptySet()) + tVar.value
|
||||
val (canBreak, body, elseStatement) = withLocalNames(namesForLoop) {
|
||||
val loopSlotPlan = SlotPlan(mutableMapOf(), 0)
|
||||
slotPlanStack.add(loopSlotPlan)
|
||||
declareSlotName(tVar.value)
|
||||
val (canBreak, body, elseStatement) = try {
|
||||
withLocalNames(namesForLoop) {
|
||||
val loopParsed = cc.parseLoop {
|
||||
if (cc.current().type == Token.Type.LBRACE) parseBlock()
|
||||
else parseStatement() ?: throw ScriptError(start, "Bad for statement: expected loop body")
|
||||
@ -2487,19 +2497,41 @@ class Compiler(
|
||||
}
|
||||
Triple(loopParsed.first, loopParsed.second, elseStmt)
|
||||
}
|
||||
} finally {
|
||||
slotPlanStack.removeLast()
|
||||
}
|
||||
val loopSlotPlanSnapshot = if (loopSlotPlan.slots.isEmpty()) emptyMap() else loopSlotPlan.slots.toMap()
|
||||
|
||||
return object : Statement() {
|
||||
override val pos: Pos = body.pos
|
||||
override suspend fun execute(scope: Scope): Obj {
|
||||
val forContext = scope.createChildScope(start)
|
||||
if (loopSlotPlanSnapshot.isNotEmpty()) {
|
||||
forContext.applySlotPlan(loopSlotPlanSnapshot)
|
||||
}
|
||||
|
||||
// loop var: StoredObject
|
||||
val loopSO = forContext.addItem(tVar.value, true, ObjNull)
|
||||
|
||||
if (constRange != null && PerfFlags.PRIMITIVE_FASTOPS) {
|
||||
val loopSlotIndex = forContext.getSlotIndexOf(tVar.value) ?: -1
|
||||
return loopIntRange(
|
||||
forContext,
|
||||
constRange.start,
|
||||
constRange.endExclusive,
|
||||
loopSO,
|
||||
loopSlotIndex,
|
||||
body,
|
||||
elseStatement,
|
||||
label,
|
||||
canBreak
|
||||
)
|
||||
}
|
||||
// insofar we suggest source object is enumerable. Later we might need to add checks
|
||||
val sourceObj = source.execute(forContext)
|
||||
|
||||
if (sourceObj is ObjRange && sourceObj.isIntRange && PerfFlags.PRIMITIVE_FASTOPS) {
|
||||
val loopSlotIndex = forContext.getSlotIndexOf(tVar.value) ?: -1
|
||||
return loopIntRange(
|
||||
forContext,
|
||||
sourceObj.start!!.toLong(),
|
||||
@ -2508,6 +2540,7 @@ class Compiler(
|
||||
else
|
||||
sourceObj.end!!.toLong(),
|
||||
loopSO,
|
||||
loopSlotIndex,
|
||||
body,
|
||||
elseStatement,
|
||||
label,
|
||||
@ -2571,13 +2604,39 @@ class Compiler(
|
||||
}
|
||||
|
||||
private suspend fun loopIntRange(
|
||||
forScope: Scope, start: Long, end: Long, loopVar: ObjRecord,
|
||||
forScope: Scope, start: Long, end: Long, loopVar: ObjRecord, loopSlotIndex: Int,
|
||||
body: Statement, elseStatement: Statement?, label: String?, catchBreak: Boolean
|
||||
): Obj {
|
||||
var result: Obj = ObjVoid
|
||||
val cacheLow = ObjInt.CACHE_LOW
|
||||
val cacheHigh = ObjInt.CACHE_HIGH
|
||||
val useCache = start >= cacheLow && end <= cacheHigh + 1
|
||||
val cache = if (useCache) ObjInt.cacheArray() else null
|
||||
val useSlot = loopSlotIndex >= 0
|
||||
if (catchBreak) {
|
||||
if (useCache && cache != null) {
|
||||
var i = start
|
||||
while (i < end) {
|
||||
val v = cache[(i - cacheLow).toInt()]
|
||||
if (useSlot) forScope.setSlotValue(loopSlotIndex, v) else loopVar.value = v
|
||||
try {
|
||||
result = body.execute(forScope)
|
||||
} catch (lbe: LoopBreakContinueException) {
|
||||
if (lbe.label == label || lbe.label == null) {
|
||||
if (lbe.doContinue) {
|
||||
i++
|
||||
continue
|
||||
}
|
||||
return lbe.result
|
||||
}
|
||||
throw lbe
|
||||
}
|
||||
i++
|
||||
}
|
||||
} else {
|
||||
for (i in start..<end) {
|
||||
loopVar.value = ObjInt.of(i)
|
||||
val v = ObjInt.of(i)
|
||||
if (useSlot) forScope.setSlotValue(loopSlotIndex, v) else loopVar.value = v
|
||||
try {
|
||||
result = body.execute(forScope)
|
||||
} catch (lbe: LoopBreakContinueException) {
|
||||
@ -2588,15 +2647,48 @@ class Compiler(
|
||||
throw lbe
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if (useCache && cache != null) {
|
||||
var i = start
|
||||
while (i < end) {
|
||||
val v = cache[(i - cacheLow).toInt()]
|
||||
if (useSlot) forScope.setSlotValue(loopSlotIndex, v) else loopVar.value = v
|
||||
result = body.execute(forScope)
|
||||
i++
|
||||
}
|
||||
} else {
|
||||
for (i in start..<end) {
|
||||
loopVar.value = ObjInt.of(i)
|
||||
val v = ObjInt.of(i)
|
||||
if (useSlot) forScope.setSlotValue(loopSlotIndex, v) else loopVar.value = v
|
||||
result = body.execute(forScope)
|
||||
}
|
||||
}
|
||||
}
|
||||
return elseStatement?.execute(forScope) ?: result
|
||||
}
|
||||
|
||||
private data class ConstIntRange(val start: Long, val endExclusive: Long)
|
||||
|
||||
private fun constIntRangeOrNull(ref: ObjRef): ConstIntRange? {
|
||||
if (ref !is RangeRef) return null
|
||||
val start = constIntValueOrNull(ref.left) ?: return null
|
||||
val end = constIntValueOrNull(ref.right) ?: return null
|
||||
val endExclusive = if (ref.isEndInclusive) end + 1 else end
|
||||
return ConstIntRange(start, endExclusive)
|
||||
}
|
||||
|
||||
private fun constIntValueOrNull(ref: ObjRef?): Long? {
|
||||
return when (ref) {
|
||||
is ConstRef -> (ref.constValue as? ObjInt)?.value
|
||||
is StatementRef -> {
|
||||
val stmt = ref.statement
|
||||
if (stmt is ExpressionStatement) constIntValueOrNull(stmt.ref) else null
|
||||
}
|
||||
else -> null
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun loopIterable(
|
||||
forScope: Scope, sourceObj: Obj, loopVar: ObjRecord,
|
||||
body: Statement, elseStatement: Statement?, label: String?,
|
||||
|
||||
@ -55,7 +55,11 @@ class ObjInt(val value: Long, override val isConst: Boolean = false) : Obj(), Nu
|
||||
|
||||
override suspend fun compareTo(scope: Scope, other: Obj): Int {
|
||||
if (other !is Numeric) return -2
|
||||
return value.compareTo(other.doubleValue)
|
||||
return if (other is ObjInt) {
|
||||
value.compareTo(other.value)
|
||||
} else {
|
||||
doubleValue.compareTo(other.doubleValue)
|
||||
}
|
||||
}
|
||||
|
||||
override fun toString(): String = value.toString()
|
||||
@ -159,13 +163,19 @@ class ObjInt(val value: Long, override val isConst: Boolean = false) : Obj(), Nu
|
||||
}
|
||||
|
||||
companion object {
|
||||
private val cache = Array(256) { ObjInt((it - 128).toLong(), true) }
|
||||
internal const val CACHE_LOW: Long = -1024L
|
||||
internal const val CACHE_HIGH: Long = 1023L
|
||||
private val cache = Array((CACHE_HIGH - CACHE_LOW + 1).toInt()) {
|
||||
ObjInt((it + CACHE_LOW).toLong(), true)
|
||||
}
|
||||
|
||||
fun of(value: Long): ObjInt {
|
||||
return if (value in -128L..127L) cache[(value + 128).toInt()]
|
||||
return if (value in CACHE_LOW..CACHE_HIGH) cache[(value - CACHE_LOW).toInt()]
|
||||
else ObjInt(value)
|
||||
}
|
||||
|
||||
internal fun cacheArray(): Array<ObjInt> = cache
|
||||
|
||||
val Zero = of(0)
|
||||
val One = of(1)
|
||||
val type = object : ObjClass("Int") {
|
||||
|
||||
@ -96,6 +96,30 @@ class ObjRange(val start: Obj?, val end: Obj?, val isEndInclusive: Boolean) : Ob
|
||||
if (other is ObjRange)
|
||||
return containsRange(scope, other)
|
||||
|
||||
if (net.sergeych.lyng.PerfFlags.PRIMITIVE_FASTOPS) {
|
||||
if (start is ObjInt && end is ObjInt && other is ObjInt) {
|
||||
val s = start.value
|
||||
val e = end.value
|
||||
val v = other.value
|
||||
if (v < s) return false
|
||||
return if (isEndInclusive) v <= e else v < e
|
||||
}
|
||||
if (start is ObjChar && end is ObjChar && other is ObjChar) {
|
||||
val s = start.value
|
||||
val e = end.value
|
||||
val v = other.value
|
||||
if (v < s) return false
|
||||
return if (isEndInclusive) v <= e else v < e
|
||||
}
|
||||
if (start is ObjString && end is ObjString && other is ObjString) {
|
||||
val s = start.value
|
||||
val e = end.value
|
||||
val v = other.value
|
||||
if (v < s) return false
|
||||
return if (isEndInclusive) v <= e else v < e
|
||||
}
|
||||
}
|
||||
|
||||
if (start == null && end == null) return true
|
||||
if (start != null) {
|
||||
if (start.compareTo(scope, other) > 0) return false
|
||||
@ -241,4 +265,3 @@ class ObjRange(val start: Obj?, val end: Obj?, val isEndInclusive: Boolean) : Ob
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -58,7 +58,7 @@ class ObjRangeIterator(val self: ObjRange) : Obj() {
|
||||
start.value.code.toLong() + nextIndex++
|
||||
else
|
||||
scope.raiseError("iterator error: unsupported range start")
|
||||
if( isCharRange ) ObjChar(x.toInt().toChar()) else ObjInt(x)
|
||||
if (isCharRange) ObjChar(x.toInt().toChar()) else ObjInt.of(x)
|
||||
}
|
||||
else {
|
||||
scope.raiseError(ObjIterationFinishedException(scope))
|
||||
@ -83,13 +83,18 @@ class ObjRangeIterator(val self: ObjRange) : Obj() {
|
||||
class ObjFastIntRangeIterator(private val start: Int, private val endExclusive: Int) : Obj() {
|
||||
|
||||
private var cur: Int = start
|
||||
private val cacheLow = ObjInt.CACHE_LOW.toInt()
|
||||
private val useCache = start >= cacheLow && endExclusive <= ObjInt.CACHE_HIGH.toInt() + 1
|
||||
private val cache = if (useCache) ObjInt.cacheArray() else null
|
||||
|
||||
override val objClass: ObjClass get() = type
|
||||
|
||||
fun hasNext(): Boolean = cur < endExclusive
|
||||
|
||||
fun next(scope: Scope): Obj =
|
||||
if (cur < endExclusive) ObjInt(cur++.toLong())
|
||||
if (cur < endExclusive) {
|
||||
if (useCache && cache != null) cache[cur++ - cacheLow] else ObjInt(cur++.toLong())
|
||||
}
|
||||
else scope.raiseError(ObjIterationFinishedException(scope))
|
||||
|
||||
companion object {
|
||||
|
||||
@ -152,6 +152,80 @@ class BinaryOpRef(private val op: BinOp, private val left: ObjRef, private val r
|
||||
|
||||
// Primitive fast paths for common cases (guarded by PerfFlags.PRIMITIVE_FASTOPS)
|
||||
if (PerfFlags.PRIMITIVE_FASTOPS) {
|
||||
// Fast membership for common containers
|
||||
if (op == BinOp.IN || op == BinOp.NOTIN) {
|
||||
val inResult: Boolean? = when (b) {
|
||||
is ObjList -> {
|
||||
if (a is ObjInt) {
|
||||
var i = 0
|
||||
val sz = b.list.size
|
||||
var found = false
|
||||
while (i < sz) {
|
||||
val v = b.list[i]
|
||||
if (v is ObjInt && v.value == a.value) {
|
||||
found = true
|
||||
break
|
||||
}
|
||||
i++
|
||||
}
|
||||
found
|
||||
} else {
|
||||
b.list.contains(a)
|
||||
}
|
||||
}
|
||||
is ObjSet -> b.set.contains(a)
|
||||
is ObjMap -> b.map.containsKey(a)
|
||||
is ObjRange -> {
|
||||
when (a) {
|
||||
is ObjInt -> {
|
||||
val s = b.start as? ObjInt
|
||||
val e = b.end as? ObjInt
|
||||
val v = a.value
|
||||
if (s == null && e == null) null
|
||||
else {
|
||||
if (s != null && v < s.value) false
|
||||
else if (e != null) if (b.isEndInclusive) v <= e.value else v < e.value else true
|
||||
}
|
||||
}
|
||||
is ObjChar -> {
|
||||
val s = b.start as? ObjChar
|
||||
val e = b.end as? ObjChar
|
||||
val v = a.value
|
||||
if (s == null && e == null) null
|
||||
else {
|
||||
if (s != null && v < s.value) false
|
||||
else if (e != null) if (b.isEndInclusive) v <= e.value else v < e.value else true
|
||||
}
|
||||
}
|
||||
is ObjString -> {
|
||||
val s = b.start as? ObjString
|
||||
val e = b.end as? ObjString
|
||||
val v = a.value
|
||||
if (s == null && e == null) null
|
||||
else {
|
||||
if (s != null && v < s.value) false
|
||||
else if (e != null) if (b.isEndInclusive) v <= e.value else v < e.value else true
|
||||
}
|
||||
}
|
||||
else -> null
|
||||
}
|
||||
}
|
||||
is ObjString -> when (a) {
|
||||
is ObjString -> b.value.contains(a.value)
|
||||
is ObjChar -> b.value.contains(a.value)
|
||||
else -> null
|
||||
}
|
||||
else -> null
|
||||
}
|
||||
if (inResult != null) {
|
||||
if (PerfFlags.PIC_DEBUG_COUNTERS) PerfStats.primitiveFastOpsHit++
|
||||
return if (op == BinOp.IN) {
|
||||
if (inResult) ObjTrue else ObjFalse
|
||||
} else {
|
||||
if (inResult) ObjFalse else ObjTrue
|
||||
}
|
||||
}
|
||||
}
|
||||
// Fast boolean ops when both operands are ObjBool
|
||||
if (a is ObjBool && b is ObjBool) {
|
||||
val r: Obj? = when (op) {
|
||||
@ -604,7 +678,37 @@ class AssignOpRef(
|
||||
else -> null
|
||||
}
|
||||
if (inPlace != null) return inPlace.asReadonly
|
||||
val result: Obj = when (op) {
|
||||
val fast: Obj? = if (PerfFlags.PRIMITIVE_FASTOPS) {
|
||||
when {
|
||||
x is ObjInt && y is ObjInt -> {
|
||||
val xv = x.value
|
||||
val yv = y.value
|
||||
when (op) {
|
||||
BinOp.PLUS -> ObjInt.of(xv + yv)
|
||||
BinOp.MINUS -> ObjInt.of(xv - yv)
|
||||
BinOp.STAR -> ObjInt.of(xv * yv)
|
||||
BinOp.SLASH -> if (yv != 0L) ObjInt.of(xv / yv) else null
|
||||
BinOp.PERCENT -> if (yv != 0L) ObjInt.of(xv % yv) else null
|
||||
else -> null
|
||||
}
|
||||
}
|
||||
(x is ObjInt || x is ObjReal) && (y is ObjInt || y is ObjReal) -> {
|
||||
val xv = if (x is ObjInt) x.doubleValue else (x as ObjReal).value
|
||||
val yv = if (y is ObjInt) y.doubleValue else (y as ObjReal).value
|
||||
when (op) {
|
||||
BinOp.PLUS -> ObjReal.of(xv + yv)
|
||||
BinOp.MINUS -> ObjReal.of(xv - yv)
|
||||
BinOp.STAR -> ObjReal.of(xv * yv)
|
||||
BinOp.SLASH -> ObjReal.of(xv / yv)
|
||||
BinOp.PERCENT -> ObjReal.of(xv % yv)
|
||||
else -> null
|
||||
}
|
||||
}
|
||||
x is ObjString && op == BinOp.PLUS -> ObjString(x.value + y.toString())
|
||||
else -> null
|
||||
}
|
||||
} else null
|
||||
val result: Obj = fast ?: when (op) {
|
||||
BinOp.PLUS -> x.plus(scope, y)
|
||||
BinOp.MINUS -> x.minus(scope, y)
|
||||
BinOp.STAR -> x.mul(scope, y)
|
||||
@ -632,7 +736,15 @@ class IncDecRef(
|
||||
// We now treat numbers as immutable and always perform write-back via setAt.
|
||||
// This avoids issues where literals are shared and mutated in-place.
|
||||
// For post-inc: return ORIGINAL value; for pre-inc: return NEW value.
|
||||
val result = if (isIncrement) v.plus(scope, one) else v.minus(scope, one)
|
||||
val result = if (PerfFlags.PRIMITIVE_FASTOPS) {
|
||||
when (v) {
|
||||
is ObjInt -> if (isIncrement) ObjInt.of(v.value + 1L) else ObjInt.of(v.value - 1L)
|
||||
is ObjReal -> if (isIncrement) ObjReal.of(v.value + 1.0) else ObjReal.of(v.value - 1.0)
|
||||
else -> if (isIncrement) v.plus(scope, one) else v.minus(scope, one)
|
||||
}
|
||||
} else {
|
||||
if (isIncrement) v.plus(scope, one) else v.minus(scope, one)
|
||||
}
|
||||
target.setAt(atPos, scope, result)
|
||||
return (if (isPost) v else result).asReadonly
|
||||
}
|
||||
@ -1447,7 +1559,7 @@ class IndexRef(
|
||||
/**
|
||||
* R-value reference that wraps a Statement (used during migration for expressions parsed as Statement).
|
||||
*/
|
||||
class StatementRef(private val statement: Statement) : ObjRef {
|
||||
class StatementRef(internal val statement: Statement) : ObjRef {
|
||||
override suspend fun get(scope: Scope): ObjRecord = statement.execute(scope).asReadonly
|
||||
}
|
||||
|
||||
@ -2283,7 +2395,19 @@ class LocalSlotRef(
|
||||
private var cachedOwnerVerified: Boolean = false
|
||||
|
||||
private fun resolveOwner(scope: Scope): Scope? {
|
||||
if (cachedOwner != null && cachedFrameId == scope.frameId && cachedOwnerVerified) return cachedOwner
|
||||
if (cachedOwner != null && cachedFrameId == scope.frameId && cachedOwnerVerified) {
|
||||
val cached = cachedOwner!!
|
||||
val candidate = if (depth == 0) scope else {
|
||||
var s: Scope? = scope
|
||||
var remaining = depth
|
||||
while (s != null && remaining > 0) {
|
||||
s = s.parent
|
||||
remaining--
|
||||
}
|
||||
s
|
||||
}
|
||||
if (candidate === cached && candidate?.getSlotIndexOf(name) == slot) return cached
|
||||
}
|
||||
var s: Scope? = scope
|
||||
var remaining = depth
|
||||
while (s != null && remaining > 0) {
|
||||
@ -2475,9 +2599,9 @@ class MapLiteralRef(private val entries: List<MapLiteralEntry>) : ObjRef {
|
||||
* Range literal: left .. right or left ..< right. Right may be omitted in certain contexts.
|
||||
*/
|
||||
class RangeRef(
|
||||
private val left: ObjRef?,
|
||||
private val right: ObjRef?,
|
||||
private val isEndInclusive: Boolean
|
||||
internal val left: ObjRef?,
|
||||
internal val right: ObjRef?,
|
||||
internal val isEndInclusive: Boolean
|
||||
) : ObjRef {
|
||||
override suspend fun get(scope: Scope): ObjRecord {
|
||||
return evalValue(scope).asReadonly
|
||||
|
||||
@ -63,6 +63,13 @@ abstract class Statement(
|
||||
|
||||
}
|
||||
|
||||
class ExpressionStatement(
|
||||
val ref: net.sergeych.lyng.obj.ObjRef,
|
||||
override val pos: Pos
|
||||
) : Statement() {
|
||||
override suspend fun execute(scope: Scope): Obj = ref.evalValue(scope)
|
||||
}
|
||||
|
||||
fun Statement.raise(text: String): Nothing {
|
||||
throw ScriptError(pos, text)
|
||||
}
|
||||
|
||||
51
lynglib/src/commonTest/kotlin/NestedRangeBenchmarkTest.kt
Normal file
51
lynglib/src/commonTest/kotlin/NestedRangeBenchmarkTest.kt
Normal file
@ -0,0 +1,51 @@
|
||||
/*
|
||||
* Copyright 2026 Sergey S. Chernov real.sergeych@gmail.com
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*
|
||||
*/
|
||||
|
||||
import kotlinx.coroutines.test.runTest
|
||||
import net.sergeych.lyng.Benchmarks
|
||||
import net.sergeych.lyng.eval
|
||||
import net.sergeych.lyng.obj.ObjInt
|
||||
import kotlin.time.TimeSource
|
||||
import kotlin.test.Test
|
||||
import kotlin.test.assertEquals
|
||||
|
||||
class NestedRangeBenchmarkTest {
|
||||
@Test
|
||||
fun benchmarkHappyNumbersNestedRanges() = runTest {
|
||||
if (!Benchmarks.enabled) return@runTest
|
||||
val script = """
|
||||
fun naiveCountHappyNumbers() {
|
||||
var count = 0
|
||||
for( n1 in 0..9 )
|
||||
for( n2 in 0..9 )
|
||||
for( n3 in 0..9 )
|
||||
for( n4 in 0..9 )
|
||||
for( n5 in 0..9 )
|
||||
for( n6 in 0..9 )
|
||||
if( n1 + n2 + n3 == n4 + n5 + n6 ) count++
|
||||
count
|
||||
}
|
||||
naiveCountHappyNumbers()
|
||||
""".trimIndent()
|
||||
|
||||
val start = TimeSource.Monotonic.markNow()
|
||||
val result = eval(script) as ObjInt
|
||||
val elapsedMs = start.elapsedNow().inWholeMilliseconds
|
||||
println("[DEBUG_LOG] [BENCH] nested-happy elapsed=${elapsedMs} ms")
|
||||
assertEquals(55252L, result.value)
|
||||
}
|
||||
}
|
||||
22
lynglib/src/jsMain/kotlin/net/sergeych/lyng/BenchmarksJs.kt
Normal file
22
lynglib/src/jsMain/kotlin/net/sergeych/lyng/BenchmarksJs.kt
Normal file
@ -0,0 +1,22 @@
|
||||
/*
|
||||
* Copyright 2026 Sergey S. Chernov real.sergeych@gmail.com
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*
|
||||
*/
|
||||
|
||||
package net.sergeych.lyng
|
||||
|
||||
actual object Benchmarks {
|
||||
actual val enabled: Boolean = false
|
||||
}
|
||||
@ -0,0 +1,28 @@
|
||||
/*
|
||||
* Copyright 2026 Sergey S. Chernov real.sergeych@gmail.com
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*
|
||||
*/
|
||||
|
||||
package net.sergeych.lyng
|
||||
|
||||
actual object Benchmarks {
|
||||
actual val enabled: Boolean = run {
|
||||
val p = System.getProperty("LYNG_BENCHMARKS")?.lowercase()
|
||||
val e = System.getenv("BENCHMARKS")?.lowercase()
|
||||
fun parse(v: String?): Boolean =
|
||||
v == "true" || v == "1" || v == "yes"
|
||||
parse(p) || parse(e)
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,33 @@
|
||||
/*
|
||||
* Copyright 2026 Sergey S. Chernov real.sergeych@gmail.com
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*
|
||||
*/
|
||||
|
||||
package net.sergeych.lyng
|
||||
|
||||
import kotlinx.cinterop.ExperimentalForeignApi
|
||||
import kotlinx.cinterop.toKString
|
||||
import platform.posix.getenv
|
||||
|
||||
@OptIn(ExperimentalForeignApi::class)
|
||||
actual object Benchmarks {
|
||||
actual val enabled: Boolean = run {
|
||||
fun parse(v: String?): Boolean =
|
||||
v == "true" || v == "1" || v == "yes"
|
||||
val b = getenv("BENCHMARKS")?.toKString()?.lowercase()
|
||||
val l = getenv("LYNG_BENCHMARKS")?.toKString()?.lowercase()
|
||||
parse(b) || parse(l)
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,22 @@
|
||||
/*
|
||||
* Copyright 2026 Sergey S. Chernov real.sergeych@gmail.com
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*
|
||||
*/
|
||||
|
||||
package net.sergeych.lyng
|
||||
|
||||
actual object Benchmarks {
|
||||
actual val enabled: Boolean = false
|
||||
}
|
||||
Loading…
x
Reference in New Issue
Block a user