Stabilize pi benchmark optimizations for release
This commit is contained in:
1 parent
a7ab0d3905
commit
064b927b1a
6 files changed
+912
-178
No files matched your search
@@ -0,0 +1,145 @@
|
||||
/*
|
||||
* 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.PerfFlags
|
||||
import net.sergeych.lyng.PerfProfiles
|
||||
import net.sergeych.lyng.Script
|
||||
import net.sergeych.lyng.Statement
|
||||
import net.sergeych.lyng.bytecode.BytecodeStatement
|
||||
import net.sergeych.lyng.bytecode.CmdCallMemberSlot
|
||||
import net.sergeych.lyng.bytecode.CmdFunction
|
||||
import net.sergeych.lyng.bytecode.CmdGetIndex
|
||||
import net.sergeych.lyng.bytecode.CmdIterPush
|
||||
import net.sergeych.lyng.bytecode.CmdMakeRange
|
||||
import net.sergeych.lyng.bytecode.CmdSetIndex
|
||||
import net.sergeych.lyng.obj.ObjString
|
||||
import java.nio.file.Files
|
||||
import java.nio.file.Path
|
||||
import kotlin.test.Test
|
||||
import kotlin.test.assertEquals
|
||||
import kotlin.test.assertTrue
|
||||
import kotlin.time.TimeSource
|
||||
|
||||
class PiSpigotBenchmarkTest {
|
||||
@Test
|
||||
fun benchmarkPiSpigot() = runTest {
|
||||
if (!Benchmarks.enabled) return@runTest
|
||||
|
||||
val source = Files.readString(resolveExample("pi-test.lyng"))
|
||||
val legacySource = source.replace(
|
||||
"val quotient = sum / denom",
|
||||
"var quotient = floor((sum / (denom * 1.0))).toInt()"
|
||||
)
|
||||
assertTrue(legacySource != source, "failed to build legacy piSpigot benchmark case")
|
||||
|
||||
val digits = 200
|
||||
val expectedSuffix = "49303819"
|
||||
|
||||
val legacyElapsed = runCase("legacy-real-division", legacySource, digits, expectedSuffix, dumpBytecode = true)
|
||||
val saved = PerfProfiles.snapshot()
|
||||
PerfFlags.RVAL_FASTPATH = false
|
||||
val optimizedRvalOffElapsed = runCase(
|
||||
"optimized-int-division-rval-off",
|
||||
source,
|
||||
digits,
|
||||
expectedSuffix,
|
||||
dumpBytecode = false
|
||||
)
|
||||
PerfProfiles.restore(saved)
|
||||
val optimizedElapsed = runCase("optimized-int-division-rval-on", source, digits, expectedSuffix, dumpBytecode = true)
|
||||
val sourceSpeedup = legacyElapsed.toDouble() / optimizedRvalOffElapsed.toDouble()
|
||||
val runtimeSpeedup = optimizedRvalOffElapsed.toDouble() / optimizedElapsed.toDouble()
|
||||
val totalSpeedup = legacyElapsed.toDouble() / optimizedElapsed.toDouble()
|
||||
println(
|
||||
"[DEBUG_LOG] [BENCH] pi-spigot compare n=$digits legacy=${legacyElapsed} ms " +
|
||||
"intDiv=${optimizedRvalOffElapsed} ms rvalOn=${optimizedElapsed} ms " +
|
||||
"intDivSpeedup=${"%.2f".format(sourceSpeedup)}x " +
|
||||
"rvalSpeedup=${"%.2f".format(runtimeSpeedup)}x " +
|
||||
"total=${"%.2f".format(totalSpeedup)}x"
|
||||
)
|
||||
}
|
||||
|
||||
private suspend fun runCase(
|
||||
name: String,
|
||||
source: String,
|
||||
digits: Int,
|
||||
expectedSuffix: String,
|
||||
dumpBytecode: Boolean,
|
||||
): Long {
|
||||
val scope = Script.newScope()
|
||||
scope.eval(source)
|
||||
|
||||
if (dumpBytecode) {
|
||||
println("[DEBUG_LOG] [BENCH] pi-spigot cmd:\n${scope.disassembleSymbol("piSpigot")}")
|
||||
dumpHotOps(scope, "piSpigot")
|
||||
}
|
||||
|
||||
val first = scope.eval("piSpigot($digits)") as ObjString
|
||||
assertEquals(expectedSuffix, first.value)
|
||||
|
||||
repeat(2) {
|
||||
val warm = scope.eval("piSpigot($digits)") as ObjString
|
||||
assertEquals(expectedSuffix, warm.value)
|
||||
}
|
||||
|
||||
val iterations = 3
|
||||
val start = TimeSource.Monotonic.markNow()
|
||||
repeat(iterations) {
|
||||
val result = scope.eval("piSpigot($digits)") as ObjString
|
||||
assertEquals(expectedSuffix, result.value)
|
||||
}
|
||||
val elapsedMs = start.elapsedNow().inWholeMilliseconds
|
||||
val avgMs = elapsedMs.toDouble() / iterations.toDouble()
|
||||
println(
|
||||
"[DEBUG_LOG] [BENCH] pi-spigot $name n=$digits iterations=$iterations " +
|
||||
"elapsed=${elapsedMs} ms avg=${"%.2f".format(avgMs)} ms"
|
||||
)
|
||||
return elapsedMs
|
||||
}
|
||||
|
||||
private fun dumpHotOps(scope: net.sergeych.lyng.Scope, name: String) {
|
||||
val fn = resolveBytecodeFunction(scope, name) ?: return
|
||||
val makeRange = fn.cmds.count { it is CmdMakeRange }
|
||||
val callMemberSlot = fn.cmds.count { it is CmdCallMemberSlot }
|
||||
val iterPush = fn.cmds.count { it is CmdIterPush }
|
||||
val getIndex = fn.cmds.count { it is CmdGetIndex }
|
||||
val setIndex = fn.cmds.count { it is CmdSetIndex }
|
||||
println(
|
||||
"[DEBUG_LOG] [BENCH] pi-spigot hot-ops " +
|
||||
"makeRange=$makeRange callMemberSlot=$callMemberSlot iterPush=$iterPush " +
|
||||
"getIndex=$getIndex setIndex=$setIndex total=${fn.cmds.size}"
|
||||
)
|
||||
}
|
||||
|
||||
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()
|
||||
}
|
||||
|
||||
private fun resolveExample(name: String): Path {
|
||||
val direct = Path.of("examples", name)
|
||||
if (Files.exists(direct)) return direct
|
||||
val parent = Path.of("..", "examples", name)
|
||||
if (Files.exists(parent)) return parent
|
||||
error("example not found: $name")
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user