VM optimizations
This commit is contained in:
1 parent
637258581d
commit
fd2da1efd3
18 files changed
+2847
-331
No files matched your search
@@ -0,0 +1,79 @@
|
||||
/*
|
||||
* 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.Script
|
||||
import net.sergeych.lyng.obj.ObjInt
|
||||
import kotlin.test.Test
|
||||
import kotlin.test.assertEquals
|
||||
import kotlin.time.TimeSource
|
||||
|
||||
class RepresentativeBenchmarkTest {
|
||||
@Test
|
||||
fun benchmarkMixedOps() = runTest {
|
||||
if (!Benchmarks.enabled) return@runTest
|
||||
val iterations = 40000
|
||||
val script = """
|
||||
fun mixedBench(n) {
|
||||
var acc = 0
|
||||
var len = 0
|
||||
val list = [1,2,3,4,5,6,7,8]
|
||||
val map = {"a":1, "b":2, "c":3}
|
||||
fun bump(x) { x + 1 }
|
||||
var i = 0
|
||||
while(i < n) {
|
||||
acc += i
|
||||
acc += bump(i)
|
||||
if( i % 2 == 0 ) acc += 3 else acc -= 1
|
||||
acc += list[i % 8]
|
||||
acc += map["a"]
|
||||
if( i % 7 == 0 ) len += 1
|
||||
if( i % 11 == 0 ) len += 1
|
||||
i++
|
||||
}
|
||||
acc + len
|
||||
}
|
||||
""".trimIndent()
|
||||
|
||||
val scope = Script.newScope()
|
||||
scope.eval(script)
|
||||
val expected = expectedValue(iterations)
|
||||
|
||||
val start = TimeSource.Monotonic.markNow()
|
||||
val result = scope.eval("mixedBench($iterations)") as ObjInt
|
||||
val elapsedMs = start.elapsedNow().inWholeMilliseconds
|
||||
println("[DEBUG_LOG] [BENCH] mixed-ops elapsed=${elapsedMs} ms")
|
||||
assertEquals(expected, result.value)
|
||||
}
|
||||
|
||||
private fun expectedValue(iterations: Int): Long {
|
||||
val list = intArrayOf(1, 2, 3, 4, 5, 6, 7, 8)
|
||||
var acc = 0L
|
||||
var len = 0L
|
||||
for (i in 0 until iterations) {
|
||||
acc += i
|
||||
acc += i + 1L
|
||||
if (i % 2 == 0) acc += 3 else acc -= 1
|
||||
acc += list[i % list.size]
|
||||
acc += 1
|
||||
if (i % 7 == 0) len += 1
|
||||
if (i % 11 == 0) len += 1
|
||||
}
|
||||
return acc + len
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,163 @@
|
||||
/*
|
||||
* 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.BytecodeBodyProvider
|
||||
import net.sergeych.lyng.Script
|
||||
import net.sergeych.lyng.Statement
|
||||
import net.sergeych.lyng.bytecode.*
|
||||
import net.sergeych.lyng.obj.ObjInt
|
||||
import kotlin.test.Test
|
||||
import kotlin.test.assertEquals
|
||||
import kotlin.time.TimeSource
|
||||
|
||||
class RepresentativeObjectBenchmarkTest {
|
||||
@Test
|
||||
fun benchmarkObjectOps() = runTest {
|
||||
if (!Benchmarks.enabled) return@runTest
|
||||
val iterations = 20000
|
||||
val script = """
|
||||
fun objectBench(n) {
|
||||
var acc = 0
|
||||
var s = ""
|
||||
val list = ["a","b","c","d","e","f","g","h","i","j"]
|
||||
val rlist = [1.0,2.0,3.0,4.0,5.0]
|
||||
val map = {"a":1, "b":2, "c":3, "d":4}
|
||||
var i = 0
|
||||
while(i < n) {
|
||||
val k = list[i % 10]
|
||||
val v = map[k] ?: 0
|
||||
val j = i % 10
|
||||
val r = i * 0.5
|
||||
val rr = rlist[j % 5]
|
||||
val oi: Object = i
|
||||
val oj: Object = j
|
||||
val or: Object = r
|
||||
val or2: Object = r + 1.0
|
||||
s = s + k
|
||||
if( k in list ) acc += 1
|
||||
if( k == "a" ) acc += v else acc -= 1
|
||||
if( k != "z" ) acc += 1
|
||||
if( k < "m" ) acc += 1 else acc -= 1
|
||||
if( k >= "d" ) acc += 1 else acc -= 1
|
||||
if( j < 7 ) acc += 1 else acc -= 1
|
||||
if( j >= 3 ) acc += 1 else acc -= 1
|
||||
if( r <= 2.5 ) acc += 1 else acc -= 1
|
||||
if( r > 3.5 ) acc += 1 else acc -= 1
|
||||
if( rr < 3.5 ) acc += 1 else acc -= 1
|
||||
if( rr >= 2.5 ) acc += 1 else acc -= 1
|
||||
if( oi == oj ) acc += 1 else acc -= 1
|
||||
if( oi < oj ) acc += 1 else acc -= 1
|
||||
if( or > or2 ) acc += 1 else acc -= 1
|
||||
if( or <= or2 ) acc += 1 else acc -= 1
|
||||
if( i % 4 == 0 ) acc += list.size
|
||||
i++
|
||||
}
|
||||
acc + s.size
|
||||
}
|
||||
""".trimIndent()
|
||||
|
||||
val scope = Script.newScope()
|
||||
scope.eval(script)
|
||||
val expected = expectedValue(iterations)
|
||||
dumpFunctionBytecode(scope, "objectBench")
|
||||
dumpFastCompareStats(scope, "objectBench")
|
||||
|
||||
val start = TimeSource.Monotonic.markNow()
|
||||
val result = scope.eval("objectBench($iterations)") as ObjInt
|
||||
val elapsedMs = start.elapsedNow().inWholeMilliseconds
|
||||
println("[DEBUG_LOG] [BENCH] object-ops elapsed=${elapsedMs} ms")
|
||||
assertEquals(expected, result.value)
|
||||
}
|
||||
|
||||
private fun expectedValue(iterations: Int): Long {
|
||||
val list = arrayOf("a","b","c","d","e","f","g","h","i","j")
|
||||
val rlist = doubleArrayOf(1.0, 2.0, 3.0, 4.0, 5.0)
|
||||
val map = mapOf("a" to 1, "b" to 2, "c" to 3, "d" to 4)
|
||||
var acc = 0L
|
||||
var s = ""
|
||||
for (i in 0 until iterations) {
|
||||
val k = list[i % list.size]
|
||||
val v = map[k] ?: 0
|
||||
val j = i % 10
|
||||
val r = i * 0.5
|
||||
val rr = rlist[j % 5]
|
||||
val oi = i
|
||||
val oj = j
|
||||
val or = r
|
||||
val or2 = r + 1.0
|
||||
s += k
|
||||
acc += 1
|
||||
if (k == "a") acc += v else acc -= 1
|
||||
if (k != "z") acc += 1
|
||||
if (k < "m") acc += 1 else acc -= 1
|
||||
if (k >= "d") acc += 1 else acc -= 1
|
||||
if (j < 7) acc += 1 else acc -= 1
|
||||
if (j >= 3) acc += 1 else acc -= 1
|
||||
if (r <= 2.5) acc += 1 else acc -= 1
|
||||
if (r > 3.5) acc += 1 else acc -= 1
|
||||
if (rr < 3.5) acc += 1 else acc -= 1
|
||||
if (rr >= 2.5) acc += 1 else acc -= 1
|
||||
if (oi == oj) acc += 1 else acc -= 1
|
||||
if (oi < oj) acc += 1 else acc -= 1
|
||||
if (or > or2) acc += 1 else acc -= 1
|
||||
if (or <= or2) acc += 1 else acc -= 1
|
||||
if (i % 4 == 0) acc += list.size
|
||||
}
|
||||
return acc + s.length
|
||||
}
|
||||
|
||||
private fun dumpFunctionBytecode(scope: net.sergeych.lyng.Scope, name: String) {
|
||||
val disasm = scope.disassembleSymbol(name)
|
||||
println("[DEBUG_LOG] [BENCH] object-ops cmd:\n$disasm")
|
||||
}
|
||||
|
||||
private fun dumpFastCompareStats(scope: net.sergeych.lyng.Scope, name: String) {
|
||||
val fn = resolveBytecodeFunction(scope, name) ?: return
|
||||
var strLocal = 0
|
||||
var intObjLocal = 0
|
||||
var realObjLocal = 0
|
||||
for (cmd in fn.cmds) {
|
||||
when (cmd) {
|
||||
is CmdCmpEqStrLocal,
|
||||
is CmdCmpNeqStrLocal,
|
||||
is CmdCmpLtStrLocal,
|
||||
is CmdCmpGteStrLocal -> strLocal += 1
|
||||
is CmdCmpEqIntObjLocal,
|
||||
is CmdCmpNeqIntObjLocal,
|
||||
is CmdCmpLtIntObjLocal,
|
||||
is CmdCmpGteIntObjLocal -> intObjLocal += 1
|
||||
is CmdCmpEqRealObjLocal,
|
||||
is CmdCmpNeqRealObjLocal,
|
||||
is CmdCmpLtRealObjLocal,
|
||||
is CmdCmpGteRealObjLocal -> realObjLocal += 1
|
||||
else -> {}
|
||||
}
|
||||
}
|
||||
println(
|
||||
"[DEBUG_LOG] [BENCH] object-ops fast-compare local cmds: str=$strLocal intObj=$intObjLocal realObj=$realObjLocal"
|
||||
)
|
||||
}
|
||||
|
||||
private fun resolveBytecodeFunction(scope: net.sergeych.lyng.Scope, name: String): CmdFunction? {
|
||||
val record = scope.get(name) ?: return null
|
||||
val stmt = record.value as? Statement ?: return null
|
||||
return (stmt as? BytecodeStatement)?.bytecodeFunction()
|
||||
?: (stmt as? BytecodeBodyProvider)?.bytecodeBody()?.bytecodeFunction()
|
||||
}
|
||||
}
|
||||
@@ -187,12 +187,24 @@ class TypesTest {
|
||||
val base = [1, 2]
|
||||
acceptInts([...base, 3])
|
||||
""".trimIndent())
|
||||
eval("""
|
||||
fun acceptReals<T: Real>(xs: List<T>) { }
|
||||
acceptReals([1.0, 2.0, 3.0])
|
||||
val base = [1.0, 2.0]
|
||||
acceptReals([...base, 3.0])
|
||||
""".trimIndent())
|
||||
assertFailsWith<net.sergeych.lyng.ScriptError> {
|
||||
eval("""
|
||||
fun acceptInts<T: Int>(xs: List<T>) { }
|
||||
acceptInts([1, "a"])
|
||||
""".trimIndent())
|
||||
}
|
||||
assertFailsWith<net.sergeych.lyng.ScriptError> {
|
||||
eval("""
|
||||
fun acceptReals<T: Real>(xs: List<T>) { }
|
||||
acceptReals([1.0, "a"])
|
||||
""".trimIndent())
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
|
||||
Reference in new issue
Block a user