views:

509

answers:

2

I'm constructing a framework in Java that will listen for events and then process them in Jython. Different event types will be sent to different scripts.

Since jython takes quite some time to compile the script when PythonInterpreter.exec() is called, I will have to pre-compile the scripts. I'm doing it the following way:

// initialize the script as string (would load it from file in final version)
String script = "print 'foo'";
// get the compiled code object
PyCode compiled = org.python.core.__builtin__.compile( script, "<>", "exec" );

The PyCode compiled object would be pushed to repository and used as events come in

PythonInterpreter pi = new PythonInterpreter();
pi.set( "variable_1", "value_1");
pi.set( "variable_x", "value_x");
pi.exec( compiled );

Now for my conundrum - it might happen that there are multiple events of certain type happening at the same time - thus multiple instances of script running at the same time.

Almost all scripts would probably remain short-lived - up to 100 lines, no loops. Number and frequency is completely random (user generated events) and could be from 0 to about 200 per second per event type.

What would the best way to do it be? I'm looking at a few possibilities:

  1. use synchronization at trigger event point - this would prevent multiple instances of same script but also events wouldn't be processed as quickly as they should be
  2. create a pool of same type scripts somehow populated by cloning original PyCode object - the biggest problem would probably be optimizing pool sizes
  3. dynamically clone the script object from the parent whenever needed and then discard it when exec() finishes - this way the lag is removed from compile but it is still present in clone method

Probably the combination of number 2 and 3 would be the best - creating dynamic pool sizes?

So, any thoughts? ;)

+2  A: 

It is a pity that PyCode instances aren't immutable (there are a lot of public members on the classes).

You can precompile a reusable script using this code:

// TODO: generate this name
final String name = "X";
byte[] scriptBytes = PyString.to_bytes(script);
CompilerFlags flags = Py.getCompilerFlags();
ByteArrayOutputStream ostream = new ByteArrayOutputStream();
Module.compile(parser.parse(new ByteArrayInputStream(scriptBytes), "exec",
    "<>", flags), ostream, name, "<>", false, false, false, flags);
byte[] buffer = ostream.toByteArray();
Class<PyRunnable> clazz = BytecodeLoader.makeClass(name, null, buffer);
final Constructor<PyRunnable> constructor = clazz
    .getConstructor(new Class[] { String.class });

You can then use the constructor to produce PyCode instances for the script whenever you need one:

 PyRunnable r = constructor.newInstance(name);
 PyCode pc = r.getMain();

I would be the first to admit that this is not a good way of doing things and probably speaks volumes about my inexperience with Jython. However, it is significantly faster than compiling every time. The code works under Jython 2.2.1, but won't compile under Jython 2.5 (nor will yours).

McDowell
Nice! Works like a charm ;)It should be noted that an instance of PythonInterpreter MUST be created before first Module.compile(...) is called. If not NullPointerException is thrown from SyspathJavaLoader.loadClass()You've been most helpful. Now all I have to do is integrate this into a dynamically re-sizable pool of scripts...
nEJC
+1  A: 

PythonInterpreter is expensive, this code will use only one.

#action.py
def execute(filename, action_locals):
    #add caching of compiled scripts here
    exec(compile(open(filename).read(), filename, 'exec'), action_locals)

//class variable, only one interpreter
PythonInterpreter pi;

//run once in init() or constructor
pi = new PythonInterpreter();//could do more initialization here
pi.exec("import action");

//every script execution
PyObject pyActionRunner = pi.eval("action.execute");
PyString pyActionName = new PyString(script_path);
PyDictionary pyActionLocals = new PyDictionary();
pyActionLocals.put("variable_1", "value_1");
pyActionLocals.put("variable_x", "value_x")
pyActionRunner.__call__(pyActionName, pyActionLocals);

#example_script.py
print variable_1, variable_x
Ron
Interesting, but as far as I know PythonInterpreter is not thread-safe, so its probably not a good idea (at least for me) ...will have to do some testing though ...
nEJC
Yes, PythonInterpreter is not thread-safe, that is exactly the reason why I've done it this way.pi.eval("action.execute") only gives you an instance of the method as a java object, does not run it.
Ron