Rhino性能和编译脚本

Rhino performance and compiled scripts

本文关键字:脚本 编译 性能 Rhino      更新时间:2023-09-26

我有一些性能问题,因为在Rhino中执行Javascript代码很慢。

我编写了以下测试,以查看执行脚本的不同方式对性能的影响:

public class SimpleScriptsPerformanceTest {
    private static int TIMES = 10000;
    // no caching scope; without optimizations
    @Test
    public void testRhino1() {
        Context ctx = Context.enter();
        try {
            ctx.setLanguageVersion(170);
            ScriptableObject scriptScope = ctx.initStandardObjects();
            long startTime = System.currentTimeMillis();
            for (int i = 0; i < TIMES; i++) {
                ctx.evaluateString(scriptScope, getScript1(), "script.js", 1, null);
            }
            long endTime = System.currentTimeMillis();
            System.out.println("Total execution time (rhino1): " + (endTime-startTime) + "ms");
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            Context.exit();
        }
    }
    // no caching scope; with optimizations
    @Test
    public void testRhino2() {
        Context ctx = Context.enter();
        try {
            ctx.setOptimizationLevel(9);
            ctx.setLanguageVersion(170);
            ScriptableObject scriptScope = ctx.initStandardObjects();
            long startTime = System.currentTimeMillis();
            for (int i = 0; i < TIMES; i++) {
                ctx.evaluateString(scriptScope, getScript1(), "script.js", 1, null);
            }
            long endTime = System.currentTimeMillis();
            System.out.println("Total execution time (rhino2): " + (endTime-startTime) + "ms");
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            Context.exit();
        }
    }
    // caching scope; with optimizations for main function and for calling code
    @Test
    public void testRhino3() {
        Context ctx = Context.enter();
        try {
            ctx.setOptimizationLevel(9);
            ctx.setLanguageVersion(170);
            ScriptableObject scriptScope = ctx.initStandardObjects();
            ctx.evaluateString(scriptScope, getScript1(), "script.js", 1, null);
            ctx.setOptimizationLevel(9);
            long startTime = System.currentTimeMillis();
            for (int i = 0; i < TIMES; i++) {
                ctx.evaluateString(scriptScope, getScript2(), "script.js", 1, null);
            }
            long endTime = System.currentTimeMillis();
            System.out.println("Total execution time (rhino3): " + (endTime-startTime) + "ms");
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            Context.exit();
        }
    }
    // caching scope; with optimizations for main function; no optimizations for calling code
    @Test
    public void testRhino4() {
        Context ctx = Context.enter();
        try {
            ctx.setOptimizationLevel(9);
            ctx.setLanguageVersion(170);
            ScriptableObject scriptScope = ctx.initStandardObjects();
            ctx.evaluateString(scriptScope, getScript1(), "script.js", 1, null);
            ctx.setOptimizationLevel(-1);
            long startTime = System.currentTimeMillis();
            for (int i = 0; i < TIMES; i++) {
                ctx.evaluateString(scriptScope, getScript2(), "script.js", 1, null);
            }
            long endTime = System.currentTimeMillis();
            System.out.println("Total execution time (rhino4): " + (endTime-startTime) + "ms");
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            Context.exit();
        }
    }
    // caching scope; without optimizations; different contexts
    @Test
    public void testRhino5() {
        ScriptableObject scriptScope = null;
        Context ctx = Context.enter();
        try {
            ctx.setOptimizationLevel(9);
            ctx.setLanguageVersion(170);
            scriptScope = ctx.initStandardObjects();
            ctx.evaluateString(scriptScope, getScript1(), "script.js", 1, null);
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            Context.exit();
        }
        ctx = Context.enter();
        try {
            ctx.setOptimizationLevel(-1);
            ctx.setLanguageVersion(170);
            long startTime = System.currentTimeMillis();
            for (int i = 0; i < TIMES; i++) {
                ctx.evaluateString(scriptScope, getScript2(), "script.js", 1, null);
            }
            long endTime = System.currentTimeMillis();
            System.out.println("Total execution time (rhino5): " + (endTime-startTime) + "ms");
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            Context.exit();
        }
    }
    // script engine; eval
    @Test
    public void testScriptEngine1() throws ScriptException {
        ScriptEngineManager manager = new ScriptEngineManager();
        ScriptEngine engine = manager.getEngineByName("js");
        long startTime = System.currentTimeMillis();
        for (int i = 0; i < TIMES; i++) {
            engine.eval(getScript1());
        }
        long endTime = System.currentTimeMillis();
        System.out.println("Total execution time (scriptEngine1): " + (endTime-startTime) + "ms");
    }
    // script engine; compiled script
    @Test
    public void testScriptEngine2() throws ScriptException {
        ScriptEngineManager manager = new ScriptEngineManager();
        ScriptEngine engine = manager.getEngineByName("js");
        Compilable compilingEngine = (Compilable)engine;
        CompiledScript script = compilingEngine.compile(getScript1());
        long startTime = System.currentTimeMillis();
        for (int i = 0; i < TIMES; i++) {
            script.eval();
        }
        long endTime = System.currentTimeMillis();
        System.out.println("Total execution time (scriptEngine2): " + (endTime-startTime) + "ms");
    }
    private String getScript1() {
        StringBuilder sb = new StringBuilder();
        sb.append("var foo = function() {").append("'n");
        sb.append("  var a = 5;").append("'n");
        sb.append("  var b = 'test';").append("'n");
        sb.append("  var c = 93.2;").append("'n");
        sb.append("  var d = [];").append("'n");
        sb.append("  for (var i = 0; i < 5; i++) {").append("'n");
        sb.append("    if (i == 2) d.push('pepe');").append("'n");
        sb.append("    else d.push('juan');").append("'n");
        sb.append("  }").append("'n");
        sb.append("  return res = a + b + c + d.join(',');").append("'n");
        sb.append("}").append("'n");
        sb.append("foo();").append("'n");
        return sb.toString();
    }
    private String getScript2() {
        StringBuilder sb = new StringBuilder();
        sb.append("foo();").append("'n");
        return sb.toString();
    }
}

多次运行脚本后,我得到了以下平均时间:

Total execution time (rhino1):        8120 ms
Total execution time (rhino2):        7946 ms
Total execution time (rhino3):        4350 ms
Total execution time (rhino4):         257 ms
Total execution time (rhino5):         188 ms
Total execution time (scriptEngine1): 1547 ms  
Total execution time (scriptEngine2): 1090 ms

所以rhino5似乎是上述最有效的,其中将函数'foo'放在作用域中只有一次,然后,在不同的上下文中,我在相同的作用域中调用该函数,根本没有优化。

从这些结果中,我得出以下结论:

  • 库和实用程序方法应该在所有优化的作用域中运行。
  • 缓存作用域,这样你就不需要再次解析脚本了。
  • 当你运行一次性脚本时,不要使用优化。
  • 不要使用ScriptEngine接口,因为它似乎比使用Rhino慢,即使使用编译脚本。

所以我的问题是:是否有一种方法可以更有效地运行Rhino脚本?

我有一些脚本不会经常更改(但确实更改并且需要在不停止应用程序的情况下更新它们),因此我可以编译和重用它们。然而,我不确定我正在做的(缓存和重用作用域)是最有效的方法。我看到有些人建议将Javascript编译成Java字节码,但不确定如何做到这一点。

指出:

    我知道这不是做微基准测试的正确方法,但是结果非常一致,在这种情况下已经足够好了。
  • 我无法切换到Java 8并使用Nashorn。
  • 我确实在java7/Rhino中阅读了编译与解释javascript的性能

Rhino引擎实现了Compilable接口。我想这会比一直计算脚本字符串快得多。

Compilable compilingEngine = (Compilable) cEngine; 
CompiledScript compiledScript = compilingEngine.compile(script);