-
Notifications
You must be signed in to change notification settings - Fork 104
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Embedded code performance #424
Comments
Jython's interpreters are not the same concept as our contexts. Contexts provide full isolation, they don't share any language state, a module imported in one context is independent from module imported in another context. And recreating all the state and reimporting all the modules (python needs a to import a lot of stuff for the core to work even if you don't import anything yourself) has a cost. Jython's intepreters have different global namespace, but they are not isolated, they share modules and other stuff. Writing into a module in one interpreter is visible in other interpreters. So having a single context is closer to what Jython is doing. Unfortunately, the context API doesn't have a way to say you just want a new namespace. The idiomatic way of running parametrized code with GraalPy is to create a function and execute it with parameters. In your example, that would be: val ctx = Context.newBuilder().option("engine.WarnInterpreterOnly", "false").build()
val fn = ctx.eval("python", """
def fn(id):
return {'name':'alen', 'id': id}
fn
""".trimIndent())
repeat(100) {
val result = fn.execute(it)
mapper.valueToTree<JsonNode>(result.`as`(Map::class.java))
} It's still a bit slower than Jython, because we just do more stuff in the first initialization, but it's much better than before. val ctx = Context.newBuilder().option("engine.WarnInterpreterOnly", "false").build()
val evalFn = ctx.eval("python", """
def eval_fn(code, namespace):
return eval(code, namespace, namespace)
eval_fn
""".trimIndent())
val createDict = ctx.eval("python", "dict")
repeat(100) {
val namespace = createDict.execute()
namespace.putHashEntry("id", it)
val result = evalFn.execute("{'name':'alen', 'id': id}", namespace)
mapper.valueToTree<JsonNode>(result.`as`(Map::class.java))
} |
Great, that makes sense, thank you for the explanation and the code samples, that helps a lot! |
Hi!
I am trying to use GraalVM from Kotlin to execute a Python script. I need to pass some input parameters, evaluate a script and read the result as a json.
I did a simple comparison with Jython:
But the Jython implementation is ~4x faster for this example. I tried to compile and reuse the script with both implementations and just inject the necessary value.
I noticed I can reuse my
val ctx = Context.newBuilder().engine(engine).build()
by moving it outside therepeat
block to make this example fast, but that is simply mutating the same context object in a loop and would retain old parameters in case they weren't reset after every execution and that seems fiddly.Am I doing something wrong? Is there a better way to evaluate scripts from Java/Kotlin code?
After revisiting this issue, I added some simple logging to track the performance and it seems that most of the Jython time is spent on script parsing:
This simple example yields almost the same amount of time on Jython with 100 or 1000 records, but GraalVM implementation scaled very poorly. Average Jython execution time is around 10 microseconds after warmup, whereas GraalVM seems to be around 10 ms after warmup, so it is actually a 1000x difference after warmup?
I must be doing something wrong with the GraalVM implementation, but I cannot figure out what.
The text was updated successfully, but these errors were encountered: