Java 8 ScriptEngine与Java 7相比存在严重的性能问题

18
我有一个使用“JavaScript” ScriptEngine (JSR-223) 的Java程序(在JDK 7u80下编译)。 我注意到,当在Java 8运行时环境(JRE 8u65)下执行时,与Java 7运行时环境(JRE 7u80)相比,我的程序运行得非常缓慢。 我已经准备了如下的SSCCE来演示问题并在同一台Windows PC上在Java 7和Java 8下执行它:
import javax.script.*;

public class SSCCE {
  public SSCCE() {
    ScriptEngineManager sem = new ScriptEngineManager();
    ScriptEngine js = sem.getEngineByName("JavaScript");
    long t = 0;
    int i = 0;

    String gJs = "function ip2long(ip) {";
    gJs += "var aIP = ip.split(\".\");";
    gJs += "return (aIP[0] * Math.pow(256, 3)) + (aIP[1] * Math.pow(256, 2)) + (aIP[2] * 256) + (aIP[3] * 1);";
    gJs += "}";
    gJs += "function long2ip(l) {";
    gJs += "if (!isNaN(l) && ((l >= 0) || (l <= Math.pow(2, 32)))) {";
    gJs += "return Math.floor(l / Math.pow(256, 3)) + \".\" +";
    gJs += "Math.floor((l % Math.pow(256, 3)) / Math.pow(256, 2)) + \".\" +";
    gJs += "Math.floor(((l % Math.pow(256, 3)) % Math.pow(256, 2)) / Math.pow(256, 1)) + \".\" +";
    gJs += "Math.floor((((l % Math.pow(256, 3)) % Math.pow(256, 2)) % Math.pow(256, 1)) / Math.pow(256, 0));";
    gJs += "}";
    gJs += "return null;";
    gJs += "}";

    try {
      js.eval(gJs);
    }
    catch (Exception e) {
      e.printStackTrace();
    }

    System.out.println("Warming Up...");

    t = System.nanoTime();

    for (i = 0; i < 4097; i++) {
      try {
        String sJs = "var ip = \"192.0.2.0\";";
        sJs += "var nip = long2ip(ip2long(ip, " + i + "));";
        js.eval(sJs);
      }
      catch (Exception e) {
        e.printStackTrace();
      }
    }

    System.out.println("Starting...");

    t = System.nanoTime();

    for (i = 0; i < 4097; i++) {
      try {
        String sJs = "var ip = \"192.0.2.0\";";
        sJs += "var nip = long2ip(ip2long(ip, " + i + "));";
        js.eval(sJs);
      }
      catch (Exception e) {
        e.printStackTrace();
      }
    }

    System.out.println("Elapsed Time: " + (System.nanoTime() - t) / 1000000000.0f);
  }

  public static void main(String[] args) {
    new SSCCE();
  }
}

JavaScript包含一个将IP地址转换为长整数的函数,加上一个数字,然后再将其转换回IP地址 - 这个过程重复4096次。
我在Java 7和Java 8之间看到以下结果:
D:\Scripts>java7 SSCCE
Warming Up...
Starting...
Elapsed Time: 0.5856594

D:\Scripts>java8 SSCCE
Warming Up...
Starting...
Elapsed Time: 4.6862915

我应该将这个问题归为与Java 8有关的性能缺陷吗?

更新:添加预热阶段,确保在计时循环之前初始化所有代码路径。


2
我无法重现你的结果,可能是因为你没有包含预热阶段。 - Tunaki
2
我建议您研究JMH以进行正确的基准测试。它是一个库,将自动包含热身阶段并避免测量错误的内容。您得到的差异可能只是因为Nahsorn加载比Rhino更慢。 - Tunaki
2
我已重新打开了这个问题:请注意,您现在得到的结果与您之前的结果完全不同(Java 8的时间从12秒变为4.5秒)。 - Tunaki
2
我现在可以重现这个结果。确实存在很大的性能差异。已经有几个与此相关的错误:JDK-8019254JDK-8034959。不确定这里是否相同。 - Tunaki
4
是的,“eval”是问题所在。Nashorn旨在通过更好地编译脚本来更快地运行它们。但每次新的“eval”都会导致一个新的编译,这可能需要很长时间。然而,几乎总有一种方法可以避免通过使用不同参数评估一个脚本来生成新脚本,例如“eval(String script,Bindings b)”。或者你可以回退到使用Rhino。 - apangin
显示剩余7条评论
1个回答

3

Java 8改进了JavaScript引擎,如果您使用编译后的compiledScript方式,则不需要每次重新评估源代码。使用eval方法时,jdk8中使用的Hashorm引擎比jdk7中使用的Rhino更慢,但更安全。

如果您寻求速度,请使用StringBuffer而非String,并使用常量Math.pow(2,32)和Math.pow(256,3)的值。

祝好


感谢所有的评论和建议。我采取了两种方法 - 第一种是先'eval'脚本,然后在可能的情况下使用'invokeFunction'。在无法使用它的领域,因为它是用户提供的JavaScript(我的用例是一个模板工具,用户可以在块中使用JavaScript),我采用了一个线程模块,使用X个线程的固定线程池并行处理模板。 - chrixm

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接