Skip to content

Instantly share code, notes, and snippets.

@pmbauer
Last active February 2, 2017 21:45
Show Gist options
  • Save pmbauer/1a9be8e51264970b1fe1ed496f931d73 to your computer and use it in GitHub Desktop.
Save pmbauer/1a9be8e51264970b1fe1ed496f931d73 to your computer and use it in GitHub Desktop.
rough semantic equivalent in java to https://twitter.com/chrishouser/status/826884726485032960

This is in response to a series of tweets by Chouser regarding java/clojure/python startup time dynamically parsing some code.

https://twitter.com/chrishouser/status/826881223486226432 https://twitter.com/chrishouser/status/826884726485032960

(bench results are normalized to my dev environment)

CODE='(-> {:parse {:some :data}} :parse :some)'

time java -jar clojure-1.8.0.jar -e "$CODE"
0m1.018s

CODE='({"parse": {"some": "data"}})["parse"]["some"]'

time python -c "print $CODE"
0m0.016s

I initially suspected the poor showing for Clojure/JVM could mostly be attributed to clojure.jar and not the JVM. A quick POC parsing some JSON is roughly 10x the example.

But to be fair to Clojure, this example is parsing and executing code not data. So this gist provides the semantic equivalent of what Clojure, Python, and Node.js are doing in pure Java.

time java pmbauer.JavaIsKindaLight 
0m0.471s

Okay, so Clojure isn't adding that much overhead. We're still several orders of magnitutde slower than python.

But why? Is it because the Java and Clojure versions are writing to disk and the python version isn't? Well ...

strace -r java pmbauer.JavaIsKindaLight 
     0.000000 execve("/usr/bin/java", ["java", "pmbauer.JavaIsKindaLight"], [/* 115 vars */]) = 0
     0.000288 brk(NULL)                 = 0x10d1000
     0.000034 access("/etc/ld.so.nohwcap", F_OK) = -1 ENOENT (No such file or directory)
 ...
     0.000037 futex(0x7fdc1b2a19d0, FUTEX_WAIT, 7052, NULLdata
) = 0
     0.842562 exit_group(0)             = ?
     0.002464 +++ exited with 0 +++

exit_group is taking up the vast majority of execution time; all of the real work is already done, java is just blocked exiting on thread cleanup. The minimal example and Clojure both exhibit this profile and simple Java applications that don't dynamically compile code do not.

package pmbauer;
import javax.tools.JavaCompiler;
import javax.tools.ToolProvider;
import java.io.File;
import java.io.IOException;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.net.URL;
import java.net.URLClassLoader;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
public class JavaIsKindaLight {
public static void main(String[] args) throws IOException, ClassNotFoundException, InstantiationException, IllegalAccessException, NoSuchMethodException, InvocationTargetException {
StringBuilder sb = new StringBuilder();
sb.append("package test;");
sb.append("import java.util.*;");
sb.append("public class ParseSomeCode { ");
sb.append(" public static Object data() {");
sb.append(" Map m = new HashMap() {{");
sb.append(" put(\"parse\", new HashMap() {{");
sb.append(" put(\"some\", \"data\");");
sb.append(" }});");
sb.append(" }};");
sb.append(" return ((Map)((Map)m.get(\"parse\"))).get(\"some\");");
sb.append(" }");
sb.append("}");
// Save source in .java file.
File root = new File("/tmp"); // On Windows running on C:\, this is C:\java.
File sourceFile = new File(root, "test/ParseSomeCode.java");
sourceFile.getParentFile().mkdirs();
Files.write(sourceFile.toPath(), sb.toString().getBytes(StandardCharsets.UTF_8));
// compile
JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();
compiler.run(null, null, null, sourceFile.getPath());
// load, run
URLClassLoader classLoader = URLClassLoader.newInstance(new URL[] { root.toURI().toURL() });
Class<?> cls = Class.forName("test.ParseSomeCode", true, classLoader);
Method method = cls.getMethod("data");
System.out.println(method.invoke(null));
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment