From 99d3c1ad433020aca3b69abffc76bbfe8cc35ec1 Mon Sep 17 00:00:00 2001 From: simbo1905 Date: Wed, 14 Feb 2018 11:17:18 +0000 Subject: [PATCH 1/3] CompiledScript alternative approach and some timings over 100 runs of each. --- src/main/java/com/winterbe/react/React.java | 52 ++++++++++++++++++- .../java/com/winterbe/react/ReactTest.java | 48 +++++++++++++++++ 2 files changed, 98 insertions(+), 2 deletions(-) diff --git a/src/main/java/com/winterbe/react/React.java b/src/main/java/com/winterbe/react/React.java index 3c3f22b..561329e 100644 --- a/src/main/java/com/winterbe/react/React.java +++ b/src/main/java/com/winterbe/react/React.java @@ -2,15 +2,43 @@ import jdk.nashorn.api.scripting.NashornScriptEngine; -import javax.script.ScriptEngineManager; -import javax.script.ScriptException; +import javax.script.*; +import java.io.BufferedReader; import java.io.InputStream; import java.io.InputStreamReader; import java.io.Reader; +import java.nio.charset.StandardCharsets; import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; +import java.util.stream.Stream; public class React { + private ThreadLocal scriptHolder = new ThreadLocal(){ + @Override + protected CompiledScript initialValue() { + + String script = Stream.of("static/nashorn-polyfill.js", + "static/vendor/react.js", + "static/vendor/showdown.min.js", + "static/commentBox.js") + .map(ThrowingFunction.wrap(React::slurp)).collect(Collectors.joining()); + + try { + ScriptEngine engine = new ScriptEngineManager().getEngineByName("JavaScript"); + Compilable compilingEngine = (Compilable) engine; + CompiledScript cs = compilingEngine.compile(script); + + Bindings bindings = engine.getBindings(ScriptContext.ENGINE_SCOPE); + cs.eval(bindings); + return cs; + } catch (ScriptException e) { + throw new RuntimeException(e); + } + } + }; + private ThreadLocal engineHolder = new ThreadLocal() { @Override protected NashornScriptEngine initialValue() { @@ -37,8 +65,28 @@ public String renderCommentBox(List comments) { } } + public String compiledRenderCommentBox(List comments) { + try { + CompiledScript cscript = scriptHolder.get(); + + Invocable invocable = (Invocable) cscript.getEngine(); + + return String.valueOf(invocable.invokeFunction("renderServer", comments)); + } + catch (Exception e) { + throw new IllegalStateException("failed to render react component", e); + } + } + private Reader read(String path) { InputStream in = getClass().getClassLoader().getResourceAsStream(path); return new InputStreamReader(in); } + + public static String slurp(String path) throws java.io.IOException { + try (InputStream resource = React.class.getClassLoader().getResourceAsStream(path)) { + return String.join("\n", new BufferedReader(new InputStreamReader(resource, + StandardCharsets.UTF_8)).lines().collect(Collectors.toList())) + "\n"; + } + } } \ No newline at end of file diff --git a/src/test/java/com/winterbe/react/ReactTest.java b/src/test/java/com/winterbe/react/ReactTest.java index 50a235f..7fa0dcb 100644 --- a/src/test/java/com/winterbe/react/ReactTest.java +++ b/src/test/java/com/winterbe/react/ReactTest.java @@ -5,16 +5,24 @@ import static org.junit.Assert.assertThat; import java.util.ArrayList; +import java.util.Date; import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; +import java.util.stream.Stream; import org.jsoup.Jsoup; import org.jsoup.nodes.Document; import org.junit.Test; +import javax.script.*; + public class ReactTest { @Test public void testRenderCommentBox() throws Exception { + + System.out.println("-------------"); List comments = new ArrayList<>(); comments.add(new Comment("Peter Parker", "This is a comment.")); comments.add(new Comment("John Doe", "This is *another* comment.")); @@ -26,6 +34,46 @@ public void testRenderCommentBox() throws Exception { Document doc = Jsoup.parse(html); assertThat(doc.select("div.comment").size(), is(2)); + + for( int i = 0; i < 100;i++ ) { + long startTime = System.nanoTime(); + react.renderCommentBox(comments); + long endTime = System.nanoTime(); + long duration = (endTime - startTime); + double milli = duration / 1e6; + System.out.println(String.format("%.2f", milli)); + } } + @Test + public void testCompiledRenderCommentBox() throws Exception { + + System.out.println("-------------"); + String script = Stream.of("static/nashorn-polyfill.js", + "static/vendor/react.js", + "static/vendor/showdown.min.js", + "static/commentBox.js") + .map(ThrowingFunction.wrap(React::slurp)).collect(Collectors.joining()); + + List comments = new ArrayList<>(); + comments.add(new Comment("Peter Parker", "This is a comment.")); + comments.add(new Comment("John Doe", "This is *another* comment.")); + + React react = new React(); + String html = react.compiledRenderCommentBox(comments); + + assertThat(html, startsWith(" Date: Wed, 14 Feb 2018 11:28:38 +0000 Subject: [PATCH 2/3] Switched to CompiledScript approach which is 13% faster in my tests --- src/main/java/com/winterbe/react/React.java | 31 -------------- .../java/com/winterbe/react/ReactTest.java | 41 ------------------- 2 files changed, 72 deletions(-) diff --git a/src/main/java/com/winterbe/react/React.java b/src/main/java/com/winterbe/react/React.java index 561329e..0648b2c 100644 --- a/src/main/java/com/winterbe/react/React.java +++ b/src/main/java/com/winterbe/react/React.java @@ -39,33 +39,7 @@ protected CompiledScript initialValue() { } }; - private ThreadLocal engineHolder = new ThreadLocal() { - @Override - protected NashornScriptEngine initialValue() { - NashornScriptEngine nashornScriptEngine = (NashornScriptEngine) new ScriptEngineManager().getEngineByName("nashorn"); - try { - nashornScriptEngine.eval(read("static/nashorn-polyfill.js")); - nashornScriptEngine.eval(read("static/vendor/react.js")); - nashornScriptEngine.eval(read("static/vendor/showdown.min.js")); - nashornScriptEngine.eval(read("static/commentBox.js")); - } catch (ScriptException e) { - throw new RuntimeException(e); - } - return nashornScriptEngine; - } - }; - public String renderCommentBox(List comments) { - try { - Object html = engineHolder.get().invokeFunction("renderServer", comments); - return String.valueOf(html); - } - catch (Exception e) { - throw new IllegalStateException("failed to render react component", e); - } - } - - public String compiledRenderCommentBox(List comments) { try { CompiledScript cscript = scriptHolder.get(); @@ -78,11 +52,6 @@ public String compiledRenderCommentBox(List comments) { } } - private Reader read(String path) { - InputStream in = getClass().getClassLoader().getResourceAsStream(path); - return new InputStreamReader(in); - } - public static String slurp(String path) throws java.io.IOException { try (InputStream resource = React.class.getClassLoader().getResourceAsStream(path)) { return String.join("\n", new BufferedReader(new InputStreamReader(resource, diff --git a/src/test/java/com/winterbe/react/ReactTest.java b/src/test/java/com/winterbe/react/ReactTest.java index 7fa0dcb..8ee1b45 100644 --- a/src/test/java/com/winterbe/react/ReactTest.java +++ b/src/test/java/com/winterbe/react/ReactTest.java @@ -22,7 +22,6 @@ public class ReactTest { @Test public void testRenderCommentBox() throws Exception { - System.out.println("-------------"); List comments = new ArrayList<>(); comments.add(new Comment("Peter Parker", "This is a comment.")); comments.add(new Comment("John Doe", "This is *another* comment.")); @@ -34,46 +33,6 @@ public void testRenderCommentBox() throws Exception { Document doc = Jsoup.parse(html); assertThat(doc.select("div.comment").size(), is(2)); - - for( int i = 0; i < 100;i++ ) { - long startTime = System.nanoTime(); - react.renderCommentBox(comments); - long endTime = System.nanoTime(); - long duration = (endTime - startTime); - double milli = duration / 1e6; - System.out.println(String.format("%.2f", milli)); - } } - @Test - public void testCompiledRenderCommentBox() throws Exception { - - System.out.println("-------------"); - String script = Stream.of("static/nashorn-polyfill.js", - "static/vendor/react.js", - "static/vendor/showdown.min.js", - "static/commentBox.js") - .map(ThrowingFunction.wrap(React::slurp)).collect(Collectors.joining()); - - List comments = new ArrayList<>(); - comments.add(new Comment("Peter Parker", "This is a comment.")); - comments.add(new Comment("John Doe", "This is *another* comment.")); - - React react = new React(); - String html = react.compiledRenderCommentBox(comments); - - assertThat(html, startsWith(" Date: Wed, 14 Feb 2018 11:41:03 +0000 Subject: [PATCH 3/3] tidy up --- src/main/java/com/winterbe/react/React.java | 6 ++-- .../com/winterbe/react/ThrowingFunction.java | 30 +++++++++++++++++++ .../java/com/winterbe/react/ReactTest.java | 2 -- 3 files changed, 32 insertions(+), 6 deletions(-) create mode 100644 src/main/java/com/winterbe/react/ThrowingFunction.java diff --git a/src/main/java/com/winterbe/react/React.java b/src/main/java/com/winterbe/react/React.java index 0648b2c..bda7054 100644 --- a/src/main/java/com/winterbe/react/React.java +++ b/src/main/java/com/winterbe/react/React.java @@ -6,10 +6,8 @@ import java.io.BufferedReader; import java.io.InputStream; import java.io.InputStreamReader; -import java.io.Reader; import java.nio.charset.StandardCharsets; import java.util.List; -import java.util.Map; import java.util.stream.Collectors; import java.util.stream.Stream; @@ -30,8 +28,8 @@ protected CompiledScript initialValue() { Compilable compilingEngine = (Compilable) engine; CompiledScript cs = compilingEngine.compile(script); - Bindings bindings = engine.getBindings(ScriptContext.ENGINE_SCOPE); - cs.eval(bindings); + // this is a noop but required to initialize the context of the compiled script + cs.eval(engine.getBindings(ScriptContext.ENGINE_SCOPE)); return cs; } catch (ScriptException e) { throw new RuntimeException(e); diff --git a/src/main/java/com/winterbe/react/ThrowingFunction.java b/src/main/java/com/winterbe/react/ThrowingFunction.java new file mode 100644 index 0000000..348455e --- /dev/null +++ b/src/main/java/com/winterbe/react/ThrowingFunction.java @@ -0,0 +1,30 @@ +package com.winterbe.react; +import java.util.function.Function; + +/** + * Checked exceptions side effects which make functional programming hard. + * Here we convert checked to unchecked which is something Spring has done for years to compose + * interfaces here we are doing it to be able to properly compose functions. + * + * https://stackoverflow.com/a/29691649/329496 + * @param + * @param + */ +@FunctionalInterface +public interface ThrowingFunction extends Function { + + @Override + public default R apply(T t) { + try { + return throwingApply(t); + } catch (Exception e) { + throw new RuntimeException(e); + } + } + + public static Function wrap(ThrowingFunction f) { + return f; + } + + R throwingApply(T t) throws Exception; +} \ No newline at end of file diff --git a/src/test/java/com/winterbe/react/ReactTest.java b/src/test/java/com/winterbe/react/ReactTest.java index 8ee1b45..8dcc0cb 100644 --- a/src/test/java/com/winterbe/react/ReactTest.java +++ b/src/test/java/com/winterbe/react/ReactTest.java @@ -15,8 +15,6 @@ import org.jsoup.nodes.Document; import org.junit.Test; -import javax.script.*; - public class ReactTest { @Test