From 1ec10a0214b411eb30545cd338002af2020c6c1b Mon Sep 17 00:00:00 2001 From: Nicolas Farabegoli Date: Thu, 18 Jun 2026 14:48:14 +0200 Subject: [PATCH 1/2] perf: cache scafi program lookup via reflection avoiding reflect invocation on each node --- .../unibo/alchemist/Scafi3Incarnation.scala | 21 ++++++++++- .../alchemist/actions/RunScafi3Program.scala | 37 +++++++++++++++---- 2 files changed, 48 insertions(+), 10 deletions(-) diff --git a/alchemist-incarnation-scafi3/src/main/scala/it/unibo/alchemist/Scafi3Incarnation.scala b/alchemist-incarnation-scafi3/src/main/scala/it/unibo/alchemist/Scafi3Incarnation.scala index 10624b54..0ac997ae 100644 --- a/alchemist-incarnation-scafi3/src/main/scala/it/unibo/alchemist/Scafi3Incarnation.scala +++ b/alchemist-incarnation-scafi3/src/main/scala/it/unibo/alchemist/Scafi3Incarnation.scala @@ -37,6 +37,20 @@ class Scafi3Incarnation[T, Position <: AlchemistPosition[Position]] extends Inca ) cl + private val reflectionCache: LoadingCache[(String, Option[URLClassLoader]), (Any, java.lang.reflect.Method)] = + Caffeine + .newBuilder() + .maximumSize(CACHE_SIZE) + .build: + case (programName, classLoader) => + val programPath = programName.split('.') + val classPath = programPath.take(programPath.length - 1).mkString("", ".", "$") + val clazz = classLoader.map(_.loadClass(classPath)).getOrElse(Class.forName(classPath)) + val module = clazz.getField("MODULE$").nn.get(clazz) + val methods = clazz.getMethods.nn + val method = methods.toList.find(_.nn.getName.nn == programPath.last).get.nn + (module, method) + override def createMolecule(s: String): Molecule = SimpleMolecule(s) @SuppressWarnings(Array("scalafix:DisableSyntax.asInstanceOf", "scalafix:DisableSyntax.null")) @@ -123,7 +137,9 @@ class Scafi3Incarnation[T, Position <: AlchemistPosition[Position]] extends Inca ): Action[T] = require(node != null, "Scafi3 requires a device and cannot execute in a Global Reaction") // scalafix:ok additionalParameters match - case params: String => RunScafi3Program[T, Position](node, environment, params) + case params: String => + val (module, method) = reflectionCache.get((params, None)).nn + RunScafi3Program[T, Position](node, environment, params, None, module, method) case params: java.util.Map[String, String] @unchecked => require( params.containsKey("code") && params.containsKey("entrypoint"), @@ -133,7 +149,8 @@ class Scafi3Incarnation[T, Position <: AlchemistPosition[Position]] extends Inca val entrypoint = params.get("entrypoint") val name = params.getOrDefault("name", "Scafi3Program") val classLoader = classLoaders.get(ProgramSource(name, code)) - RunScafi3Program[T, Position](node, environment, entrypoint, Some(classLoader)) + val (module, method) = reflectionCache.get((entrypoint, Some(classLoader))).nn + RunScafi3Program[T, Position](node, environment, entrypoint, Some(classLoader), module, method) case params => throw IllegalArgumentException( s"Invalid parameters for Scafi3. `String` required, but ${params.getClass} has been provided: $params", diff --git a/alchemist-incarnation-scafi3/src/main/scala/it/unibo/alchemist/actions/RunScafi3Program.scala b/alchemist-incarnation-scafi3/src/main/scala/it/unibo/alchemist/actions/RunScafi3Program.scala index 4cc7c9d3..453e8c66 100644 --- a/alchemist-incarnation-scafi3/src/main/scala/it/unibo/alchemist/actions/RunScafi3Program.scala +++ b/alchemist-incarnation-scafi3/src/main/scala/it/unibo/alchemist/actions/RunScafi3Program.scala @@ -24,15 +24,36 @@ class RunScafi3Program[T, Position <: AlchemistPosition[Position]]( node: Node[T], environment: Environment[T, Position], val programName: String, - val classLoader: Option[URLClassLoader] = None, + val classLoader: Option[URLClassLoader], + val module: Any, + val method: java.lang.reflect.Method, ) extends AbstractAction[T](node): + + def this(node: Node[T], environment: Environment[T, Position], programName: String, classLoader: Option[URLClassLoader]) = + this( + node, + environment, + programName, + classLoader, + { + val programPath = programName.split('.') + val classPath = programPath.take(programPath.length - 1).mkString("", ".", "$") + val clazz = classLoader.map(_.loadClass(classPath)).getOrElse(Class.forName(classPath)) + clazz.getField("MODULE$").nn.get(clazz) + }, + { + val programPath = programName.split('.') + val classPath = programPath.take(programPath.length - 1).mkString("", ".", "$") + val clazz = classLoader.map(_.loadClass(classPath)).getOrElse(Class.forName(classPath)) + val methods = clazz.getMethods.nn + methods.toList.find(_.nn.getName.nn == programPath.last).get.nn + } + ) + + def this(node: Node[T], environment: Environment[T, Position], programName: String) = + this(node, environment, programName, None) + private val programIdentifier = SimpleMolecule(programName) - private val programPath: Array[String] = programName.split('.') - private val classPath: String = programPath.take(programPath.length - 1).mkString("", ".", "$") - private val clazz = classLoader.map(_.loadClass(classPath)).getOrElse(Class.forName(classPath)) - private val module = clazz.getField("MODULE$").nn.get(clazz) - private val methods = clazz.getMethods.nn - private val method = methods.toList.find(_.nn.getName.nn == programPath.last).get.nn val localDevice: Scafi3Device[T, Position] = node.asProperty(classOf[Scafi3Device[T, Position]]) @@ -46,7 +67,7 @@ class RunScafi3Program[T, Position <: AlchemistPosition[Position]]( override def getContext: Context = Context.NEIGHBORHOOD override def cloneAction(node: Node[T], reaction: Reaction[T]): Action[T] = - RunScafi3Program[T, Position](node, environment, programName, classLoader) + RunScafi3Program[T, Position](node, environment, programName, classLoader, module, method) override def execute(): Unit = val result = scafiProgram.cycle() From 588bfdd168a711c35b78c94bb54d087d89089a9c Mon Sep 17 00:00:00 2001 From: Nicolas Farabegoli Date: Thu, 18 Jun 2026 14:54:17 +0200 Subject: [PATCH 2/2] style: code refactoring --- .../alchemist/actions/RunScafi3Program.scala | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/alchemist-incarnation-scafi3/src/main/scala/it/unibo/alchemist/actions/RunScafi3Program.scala b/alchemist-incarnation-scafi3/src/main/scala/it/unibo/alchemist/actions/RunScafi3Program.scala index 453e8c66..e948b65b 100644 --- a/alchemist-incarnation-scafi3/src/main/scala/it/unibo/alchemist/actions/RunScafi3Program.scala +++ b/alchemist-incarnation-scafi3/src/main/scala/it/unibo/alchemist/actions/RunScafi3Program.scala @@ -29,25 +29,28 @@ class RunScafi3Program[T, Position <: AlchemistPosition[Position]]( val method: java.lang.reflect.Method, ) extends AbstractAction[T](node): - def this(node: Node[T], environment: Environment[T, Position], programName: String, classLoader: Option[URLClassLoader]) = + def this( + node: Node[T], + environment: Environment[T, Position], + programName: String, + classLoader: Option[URLClassLoader], + ) = this( node, environment, programName, - classLoader, - { + classLoader, { val programPath = programName.split('.') val classPath = programPath.take(programPath.length - 1).mkString("", ".", "$") val clazz = classLoader.map(_.loadClass(classPath)).getOrElse(Class.forName(classPath)) clazz.getField("MODULE$").nn.get(clazz) - }, - { + }, { val programPath = programName.split('.') val classPath = programPath.take(programPath.length - 1).mkString("", ".", "$") val clazz = classLoader.map(_.loadClass(classPath)).getOrElse(Class.forName(classPath)) val methods = clazz.getMethods.nn methods.toList.find(_.nn.getName.nn == programPath.last).get.nn - } + }, ) def this(node: Node[T], environment: Environment[T, Position], programName: String) =