QtScript: Speeding up repeated evaluation

Observe the following code snippet:

QScriptEngine engine;
for (int i = 0; i < 42000; ++i)

In the preceding code, the QScriptEngine::evaluate() function is called repeatedly with the same input; the engine will compile this input anew each time, and thus is doing a lot of redundant work. Wouldn't it be nice if we could "compile once, execute any number of times"(tm)?

Well, one option would be to introduce a "CompiledScript" class, a QScriptEngine::compile() function that returns the compiled version of your script code, and an overload of evaluate() that takes a "CompiledScript" as argument. But do we really need all that stuff? Nah; you can easily achieve the intended effect with the current API, with one line of extra code. How? By using function expressions:

QScriptEngine engine;
QScriptValue fun = engine.evaluate("function() { ++x; }");
for (int i = 0; i < 42000; ++i)

Before entering the loop, we evaluate (=compile) a function expression, which has as its body the code we wish to evaluate (=execute) several times. In the loop, we simply call() the resulting function. You can deal with script exceptions the same way you do when using evaluate(); e.g. if some error occurs, the return value from call() will be an Error object.

The code using function expressions is about 20 times faster than the code in the first snippet.

Perhaps you have one or more variables that you want to substitute into a script before evaluating it. Maybe you'd call evaluate() like this:

for (int i = 0; i < 42000; ++i)
engine.evaluate(QString::fromLatin1("x += %0").arg(i));

So when e.g. i is 123, "x += 123" would be passed to evaluate(). Well, you can use function expressions in cases like this too; simply define a function expression that manipulates its arguments in the desired way, then pass the actual arguments when calling the function:

QScriptValue fun = engine.evaluate("function(a) { x += a; }");
QScriptValue thisObject = engine.globalObject();
for (int i = 0; i < 42000; ++i) {
QScriptValueList args;
args << QScriptValue(&engine, i);
fun.call(thisObject, args);

In this case, using a function expression is 11 times faster than plain calls to evaluate().

Another, not performance-centric, yet pleasant effect of using function expressions is that you can declare variables that are local to the function invocation, e.g. you avoid things unintentionally/unnecessarily ending up in the engine's global environment:

engine.evaluate("function() { var foo = 123; }").call();

If you passed the body of the above function expression (var foo = 123) to evaluate(), you would end up with a global variable foo, overwriting whatever value was previously stored in that variable; perhaps not what you intended.

Blog Topics: