# HG changeset patch # User Jaroslav Tulach # Date 1453779383 -3600 # Node ID 4ce38f21f4cddc4d2c879309e88235590b477db1 # Parent 727d3be7e03c3da7fd9afee97774b2f4fed035f8 JavaScript in a browser can be at most three times slower than HotSpot when computing five or ten thousands of prime numbers diff -r 727d3be7e03c -r 4ce38f21f4cd benchmarks/sieve/pom.xml --- a/benchmarks/sieve/pom.xml Mon Jan 25 08:16:33 2016 +0100 +++ b/benchmarks/sieve/pom.xml Tue Jan 26 04:36:23 2016 +0100 @@ -38,6 +38,15 @@ + org.apache.maven.plugins + maven-surefire-plugin + + + brwsr + + + + org.codehaus.mojo xml-maven-plugin 1.0 diff -r 727d3be7e03c -r 4ce38f21f4cd benchmarks/sieve/src/test/java/org/apidesign/benchmark/sieve/SieveTest.java --- a/benchmarks/sieve/src/test/java/org/apidesign/benchmark/sieve/SieveTest.java Mon Jan 25 08:16:33 2016 +0100 +++ b/benchmarks/sieve/src/test/java/org/apidesign/benchmark/sieve/SieveTest.java Tue Jan 26 04:36:23 2016 +0100 @@ -36,7 +36,7 @@ return (int) System.currentTimeMillis(); } - @Compare(scripting = false) + @Compare public int oneThousand() throws IOException { SieveTest sieve = new SieveTest(); int now = time(); @@ -46,7 +46,7 @@ return res; } - @Compare(scripting = false) + @Compare(slowdown = 3.0) public int fiveThousand() throws IOException { SieveTest sieve = new SieveTest(); int now = time(); @@ -55,6 +55,16 @@ log("oneThousand in " + took + " ms"); return res; } + + @Compare(slowdown = 3.0) + public int tenThousand() throws IOException { + SieveTest sieve = new SieveTest(); + int now = time(); + int res = sieve.compute(10000); + int took = time() - now; + log("oneThousand in " + took + " ms"); + return res; + } @Factory public static Object[] create() { diff -r 727d3be7e03c -r 4ce38f21f4cd launcher/api/src/main/java/org/apidesign/bck2brwsr/launcher/InvocationContext.java --- a/launcher/api/src/main/java/org/apidesign/bck2brwsr/launcher/InvocationContext.java Mon Jan 25 08:16:33 2016 +0100 +++ b/launcher/api/src/main/java/org/apidesign/bck2brwsr/launcher/InvocationContext.java Tue Jan 26 04:36:23 2016 +0100 @@ -19,7 +19,6 @@ import java.io.IOException; import java.io.InputStream; -import java.net.URI; import java.util.ArrayList; import java.util.List; import java.util.concurrent.CountDownLatch; @@ -38,6 +37,7 @@ private Throwable exception; String html; final List resources = new ArrayList<>(); + private int time; InvocationContext(Launcher launcher, Class clazz, String methodName) { this.launcher = launcher; @@ -65,11 +65,27 @@ /** Invokes the associated method. * @return the textual result of the invocation + * @throws java.io.IOException if execution fails */ public String invoke() throws IOException { launcher.runMethod(this); return toString(); } + + /** Invokes the associated method. + * @param time one element array to store the length of the invocation + * - can be null + * @return the textual result of the invocation + * @throws java.io.IOException if execution fails + * @since 0.20 + */ + public String invoke(int[] time) throws IOException { + launcher.runMethod(this); + if (time != null) { + time[0] = this.time; + } + return toString(); + } /** Obtains textual result of the invocation. * @return text representing the exception or result value @@ -90,7 +106,8 @@ wait.await(timeOut, TimeUnit.MILLISECONDS); } - void result(String r, Throwable e) { + void result(String r, int time, Throwable e) { + this.time = time; this.result = r; this.exception = e; wait.countDown(); diff -r 727d3be7e03c -r 4ce38f21f4cd launcher/fx/src/main/java/org/apidesign/bck2brwsr/launcher/BaseHTTPLauncher.java --- a/launcher/fx/src/main/java/org/apidesign/bck2brwsr/launcher/BaseHTTPLauncher.java Mon Jan 25 08:16:33 2016 +0100 +++ b/launcher/fx/src/main/java/org/apidesign/bck2brwsr/launcher/BaseHTTPLauncher.java Tue Jan 26 04:36:23 2016 +0100 @@ -337,10 +337,20 @@ public void service(Request request, Response response) throws Exception { String id = request.getParameter("request"); String value = request.getParameter("result"); + String timeText = request.getParameter("time"); if (value != null && value.indexOf((char)0xC5) != -1) { value = toUTF8(value); } - + int time; + if (timeText != null) { + try { + time = (int) Double.parseDouble(timeText); + } catch (NumberFormatException numberFormatException) { + time = 0; + } + } else { + time = 0; + } InvocationContext mi = null; int caseNmbr = -1; @@ -349,7 +359,7 @@ LOG.log(Level.INFO, "Received result for case {0} = {1}", new Object[]{id, value}); value = decodeURL(value); int indx = Integer.parseInt(id); - cases.get(indx).result(value, null); + cases.get(indx).result(value, time, null); if (++indx < cases.size()) { mi = cases.get(indx); LOG.log(Level.INFO, "Re-executing case {0}", indx); diff -r 727d3be7e03c -r 4ce38f21f4cd launcher/fx/src/main/java/org/apidesign/bck2brwsr/launcher/fximpl/Console.java --- a/launcher/fx/src/main/java/org/apidesign/bck2brwsr/launcher/fximpl/Console.java Mon Jan 25 08:16:33 2016 +0100 +++ b/launcher/fx/src/main/java/org/apidesign/bck2brwsr/launcher/fximpl/Console.java Tue Jan 26 04:36:23 2016 +0100 @@ -43,7 +43,10 @@ private static native void setAttr(String id, String attr, Object value); @JavaScriptBody(args = { "elem", "attr", "value" }, body = "elem[attr] = value;") - private static native void setAttr(Object id, String attr, Object value); + private static native void setAttr(Object elem, String attr, Object value); + + @JavaScriptBody(args = {}, body = "return new Date().getTime()") + private static native double getTime(); private static void closeWindow() {} @@ -175,9 +178,11 @@ log("Processing \"" + arr[0] + "\" for " + retries + " time"); } Object result = retries++ >= 100 ? "java.lang.InterruptedException:timeout(" + retries + ")" : c.runTest(); + String reply = "?request=" + c.getRequestId() + "&time=" + c.time + "&result=" + result; + log("Sending back: ..." + reply); finishTest(c, result); - String u = url + "?request=" + c.getRequestId() + "&result=" + result; + String u = url + reply; new Request(url, u); } catch (Exception ex) { if (ex instanceof InterruptedException) { @@ -264,6 +269,7 @@ private static final class Case { private final Object data; + private double time; private Object inst; private Case(Object data) { @@ -315,8 +321,8 @@ Object result = invokeMethod(this.getClassName(), this.getMethodName()); setAttr("bck2brwsr.fragment", "innerHTML", ""); log("Result: " + result); + log("Time: " + time + " ms"); result = encodeURL("" + result); - log("Sending back: ...?request=" + this.getRequestId() + "&result=" + result); return result; } @@ -340,7 +346,11 @@ if (inst == null) { inst = c.newInstance(); } + double now = getTime(); res = found.invoke(inst); + double took = Math.round(getTime() - now); + time += took; + log("Execution took " + took + " ms"); } } catch (Throwable ex) { if (ex instanceof InvocationTargetException) { diff -r 727d3be7e03c -r 4ce38f21f4cd launcher/http/src/main/java/org/apidesign/bck2brwsr/launcher/JSLauncher.java --- a/launcher/http/src/main/java/org/apidesign/bck2brwsr/launcher/JSLauncher.java Mon Jan 25 08:16:33 2016 +0100 +++ b/launcher/http/src/main/java/org/apidesign/bck2brwsr/launcher/JSLauncher.java Tue Jan 26 04:36:23 2016 +0100 @@ -58,9 +58,9 @@ mi.clazz.getName(), mi.methodName).toString(); time = System.currentTimeMillis() - time; LOG.log(Level.FINE, "Resut of {0}.{1} = {2} in {3} ms", new Object[]{mi.clazz.getName(), mi.methodName, res, time}); - mi.result(res, null); + mi.result(res, (int)time, null); } catch (ScriptException | NoSuchMethodException ex) { - mi.result(null, ex); + mi.result(null, -1, ex); } return mi; } diff -r 727d3be7e03c -r 4ce38f21f4cd launcher/http/src/main/java/org/apidesign/bck2brwsr/launcher/impl/Console.java --- a/launcher/http/src/main/java/org/apidesign/bck2brwsr/launcher/impl/Console.java Mon Jan 25 08:16:33 2016 +0100 +++ b/launcher/http/src/main/java/org/apidesign/bck2brwsr/launcher/impl/Console.java Tue Jan 26 04:36:23 2016 +0100 @@ -53,6 +53,9 @@ "elem[attr] = value;") private static native void setAttr(Object id, String attr, Object value); + @net.java.html.js.JavaScriptBody(args = {}, body = "return new Date().getTime()") + private static native double getTime(); + @net.java.html.js.JavaScriptBody(args = { }, body = "var a = document.createElement('a');" + "a.innerHTML = 'Cancel: closing in 10s...';\n" @@ -180,9 +183,11 @@ log("Processing \"" + arr[0] + "\" for " + retries + " time"); } Object result = retries++ >= 100 ? "java.lang.InterruptedException:timeout(" + retries + ")" : c.runTest(); + String reply = "?request=" + c.getRequestId() + "&time=" + c.time + "&result=" + result; + log("Sending back: ..." + reply); finishTest(c, result); - String u = url + "?request=" + c.getRequestId() + "&result=" + result; + String u = url + reply; new Request(url, u); } catch (Exception ex) { if (ex instanceof InterruptedException) { @@ -285,6 +290,7 @@ private static final class Case { private final Object data; private Object inst; + private double time; private Case(Object data) { this.data = data; @@ -332,7 +338,6 @@ setAttr("bck2brwsr.fragment", "innerHTML", ""); log("Result: " + result); result = encodeURL("" + result); - log("Sending back: ...?request=" + this.getRequestId() + "&result=" + result); return result; } @@ -350,14 +355,19 @@ Object res; if (found != null) { try { + double now; if ((found.getModifiers() & Modifier.STATIC) != 0) { + now = getTime(); res = found.invoke(null); } else { if (inst == null) { inst = c.newInstance(); } + now = getTime(); res = found.invoke(inst); } + double took = Math.round((float)(getTime() - now)); + time += took; } catch (Throwable ex) { if (ex instanceof InvocationTargetException) { ex = ((InvocationTargetException) ex).getTargetException(); diff -r 727d3be7e03c -r 4ce38f21f4cd rt/vmtest/src/main/java/org/apidesign/bck2brwsr/vmtest/Compare.java --- a/rt/vmtest/src/main/java/org/apidesign/bck2brwsr/vmtest/Compare.java Mon Jan 25 08:16:33 2016 +0100 +++ b/rt/vmtest/src/main/java/org/apidesign/bck2brwsr/vmtest/Compare.java Tue Jan 26 04:36:23 2016 +0100 @@ -42,4 +42,11 @@ * @return */ boolean scripting() default true; + + /** Compare (or not) execution times. If the value is specified and + * bigger than zero, then the fastest and slowest execution time is + * compared and if the ratio between them is higher, the compare fails. + * @return ratio (e.g. 2x, 3x, 3.5x) between execution times + */ + double slowdown() default -1; } diff -r 727d3be7e03c -r 4ce38f21f4cd rt/vmtest/src/main/java/org/apidesign/bck2brwsr/vmtest/impl/Bck2BrwsrCase.java --- a/rt/vmtest/src/main/java/org/apidesign/bck2brwsr/vmtest/impl/Bck2BrwsrCase.java Mon Jan 25 08:16:33 2016 +0100 +++ b/rt/vmtest/src/main/java/org/apidesign/bck2brwsr/vmtest/impl/Bck2BrwsrCase.java Tue Jan 26 04:36:23 2016 +0100 @@ -47,6 +47,7 @@ private final Http.Resource[] http; private final InvocationContext c; Object value; + int time; Bck2BrwsrCase(Method m, String type, Launcher l, boolean fail, HtmlFragment html, Http.Resource[] http) { this.l = l; @@ -75,8 +76,10 @@ } } } - String res = c.invoke(); - value = res; + int[] time = { 0 }; + String res = c.invoke(time); + this.value = res; + this.time = time[0]; if (fail) { int idx = res == null ? -1 : res.indexOf(':'); if (idx >= 0) { @@ -111,7 +114,9 @@ } } else { try { + long now = System.currentTimeMillis(); value = m.invoke(m.getDeclaringClass().newInstance()); + time = (int) (System.currentTimeMillis() - now); } catch (InvocationTargetException ex) { Throwable t = ex.getTargetException(); value = t.getClass().getName() + ":" + t.getMessage(); diff -r 727d3be7e03c -r 4ce38f21f4cd rt/vmtest/src/main/java/org/apidesign/bck2brwsr/vmtest/impl/CompareCase.java --- a/rt/vmtest/src/main/java/org/apidesign/bck2brwsr/vmtest/impl/CompareCase.java Mon Jan 25 08:16:33 2016 +0100 +++ b/rt/vmtest/src/main/java/org/apidesign/bck2brwsr/vmtest/impl/CompareCase.java Tue Jan 26 04:36:23 2016 +0100 @@ -40,11 +40,13 @@ public final class CompareCase implements ITest { private final Bck2BrwsrCase first, second; private final Method m; + private final double slowdown; - private CompareCase(Method m, Bck2BrwsrCase first, Bck2BrwsrCase second) { + private CompareCase(Method m, Bck2BrwsrCase first, Bck2BrwsrCase second, double slowdown) { this.first = first; this.second = second; this.m = m; + this.slowdown = slowdown; } /** Inspects clazz and for each {@lik Compare} method creates @@ -120,6 +122,20 @@ Bck2BrwsrCase.dumpJS(sb, second); throw new AssertionError(sb.toString()); } + if (slowdown > 0.0) { + Bck2BrwsrCase slow; + Bck2BrwsrCase fast; + if (first.time >= second.time) { + slow = second; + fast = first; + } else { + fast = second; + slow = first; + } + if (slow.time * slowdown < fast.time) { + Assert.fail("Too slow " + slow.getTestName() + " took " + slow.time + " ms vs. " + fast.time + " ms of " + fast.getTestName()); + } + } } /** Test name. @@ -135,19 +151,24 @@ if (c == null) { return; } + String slowdownOverride = System.getProperty("vmtest.slowdown"); final Bck2BrwsrCase real = new Bck2BrwsrCase(m, "Java", null, false, null, null); ret.add(real); + double slowdown = c.slowdown(); + if (slowdown > 0.0 && slowdownOverride != null) { + slowdown = Double.parseDouble(slowdownOverride); + } if (c.scripting()) { final Bck2BrwsrCase js = new Bck2BrwsrCase(m, "JavaScript", l.javaScript(), false, null, null); ret.add(js); - ret.add(new CompareCase(m, real, js)); + ret.add(new CompareCase(m, real, js, slowdown)); } for (String b : brwsr) { final Launcher s = l.brwsr(b); ret.add(s); final Bck2BrwsrCase cse = new Bck2BrwsrCase(m, b, s, false, null, null); ret.add(cse); - ret.add(new CompareCase(m, real, cse)); + ret.add(new CompareCase(m, real, cse, slowdown)); } } private static void registerBrwsrCases(Class brwsrTest, Method m, final LaunchSetup l, List ret, String[] brwsr) {