18

I have a Java program (compiled using JDK 7u80) which makes extensive use of the "JavaScript" ScriptEngine (JSR-223). I have noticed that my program runs extremely slow when executed under a Java 8 runtime environment (JRE 8u65) in comparison to a Java 7 runtime environment (JRE 7u80).

I have put together the following SSCCE to demonstrate the problem and then executed it under Java 7 and Java 8 on the same Windows PC:

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();
  }
}

The JavaScript consists of a function which converts an IP address to a long, adds a number and then converts it to back an IP address - this is repeated 4096 times.

I am seeing the following results between Java 7 and Java 8:

D:\Scripts>java7 SSCCE
Warming Up...
Starting...
Elapsed Time: 0.5856594

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

Should I be raising this as a performance bug associated with Java 8?

UPDATED: To include a warm up phase to ensure all code paths have been initialized before my timing loop.

chrixm
  • 942
  • 6
  • 26
  • 2
    I can't reproduce your results, probably because you did not include a warmup phase. – Tunaki Nov 08 '15 at 16:48
  • How is this a duplicate of a question which doesn't mention anything about performance differences between Java 7 and Java 8? It is a post which mentions how to do micro-benchmarks. If I read that post correctly I should be doing the loop twice, but only timing it on the second time? – chrixm Nov 08 '15 at 17:15
  • 2
    I suggest you look into JMH to do correct benchmarks. It is a library that will automatically include a warmup phase and avoid measuring the wrong thing. The difference you are getting might just be because Nahsorn takes longer to load than Rhino. – Tunaki Nov 08 '15 at 17:17
  • If you look at the code you will see that the ScriptEngine has already been used and "initialised" before I perform any timing functions so it is unlikely going to be a difference between loading the engine. Also, the more iterations, the bigger the gap I am seeing between Java 7 and Java 8. I will update my question to include a warm up. – chrixm Nov 08 '15 at 17:23
  • 2
    I've reopened the question: note that the result you now get are completely different than the one you had (4.5s instead of 12s for Java 8) – Tunaki Nov 08 '15 at 17:31
  • 2
    I can now reproduce the result. There is indeed a big performance difference. There a couple of bug related to this already: [JDK-8019254](https://bugs.openjdk.java.net/browse/JDK-8019254), [JDK-8034959](https://bugs.openjdk.java.net/browse/JDK-8034959). Not sure if this is the same here. – Tunaki Nov 08 '15 at 17:40
  • The second bug [JDK-8034959](https://bugs.openjdk.java.net/browse/JDK-8034959) looks plausible as I am calling `eval` multiple times. However, it looks like no one has commented on the bug since 2014. – chrixm Nov 08 '15 at 17:56
  • 4
    Yes, `eval` is the problem. Nashorn was meant to *run* scripts faster by *compiling* them better. But each new `eval` results in a new compilation that can take long. However there's nearly always a way to avoid generation of a new script by evaluating one script with different parameters, e.g. `eval(String script, Bindings b)`. Alternatively you can fallback to using Rhino. – apangin Nov 08 '15 at 22:54
  • I have a program which processes user provided templates, which can contain multiple script type blocks that use JavaScript. The same template is processed multiple times for data in a database hence the difficulty in running it in a single eval. Looks like I am going to need to play around to try and workaround this design decision with nashorn. – chrixm Nov 08 '15 at 23:09
  • @chrixm as apangin said, you can always just use Rhino, you just need to provide it as a jar file since it doesn't come with Java 8. – David Conrad Nov 10 '15 at 00:34
  • So looks like I have 3 options - 1) do nothing and live with the performance issue and hope they improve it in Java 9, 2) try and change my code to reduce the amount of evals or 3) make my distribution massive in comparison (97k jar file at the moment) by bundling Rhino. – chrixm Nov 10 '15 at 14:43
  • It probably isn't optimised yet, but the latest EA build (b91) of Java 9 has the same test taking 12.8 seconds :( – chrixm Nov 10 '15 at 15:19

1 Answers1

3

Java 8 improve the JavaScript engine, if you use the compile compiledScript way, to not reevaluate the source code each time. With the eval method, Hashorm engine used in jdk8 is slower than Rhino used in jdk7, but more secure.

Prefer a StringBuffer vs a String , and usage of constants for Math.pow(2, 32) and Math.pow(256, 3) values if you looking for speed...

Yours

hgregoire
  • 111
  • 1
  • 3
  • Thanks for all the comments and suggestions. I have adopted a two prong approach - the first is to 'eval' the script once and then use 'invokeFunction' where possible. In areas where this isn't possible because it is user provided JavaScript (my use case is a templating tool where the user can use JavaScript in blocks), I have adopted a threading module using a fixed thread pool of X threads to process templates in parallel. – chrixm Nov 28 '15 at 14:37