From 8d8160d34106a6a4f246b844f2923d4484bf7679 Mon Sep 17 00:00:00 2001 From: Shautvast Date: Sat, 3 Jun 2023 10:13:07 +0200 Subject: [PATCH] Finally found and removed the performance bottleneck, so now performance is equal to jackson updated dependencies and surefire config for java 9+ --- README.md | 3 + pom.xml | 45 +++----- .../jsontoy/serialize/JSONSerializer.java | 4 +- .../serialize/SynthSerializerFactory.java | 106 ++++++++---------- ...ackson.java => JacksonComparisonTest.java} | 27 +++-- 5 files changed, 80 insertions(+), 105 deletions(-) rename src/test/java/nl/jssl/jsontoy/serialize/performance/{Jackson.java => JacksonComparisonTest.java} (70%) diff --git a/README.md b/README.md index 3b88b89..9675f6b 100644 --- a/README.md +++ b/README.md @@ -4,3 +4,6 @@ a JSON serializer based on bytecode manipulation * creates a Json serializer for a java type using javassist * deserializing not yet implemented * see the unit tests to see how it works + + +* as of java9 it needs `--add-opens java.base/java.lang=ALL-UNNAMED` as java commandline option. \ No newline at end of file diff --git a/pom.xml b/pom.xml index dfe5682..57427d9 100644 --- a/pom.xml +++ b/pom.xml @@ -15,47 +15,21 @@ - - commons-io - commons-io - 2.6 - - org.javassist javassist 3.26.0-GA - - - ch.qos.logback - logback-core - 1.2.3 - - - - org.slf4j - slf4j-api - 1.7.30 - - - - ch.qos.logback - logback-classic - 1.2.3 - - - junit junit - [4.13.1,) + 4.13.2 test org.assertj assertj-core - 1.6.0 + 3.23.1 test @@ -68,8 +42,21 @@ com.fasterxml.jackson.core jackson-databind - 2.10.5.1 + 2.15.1 test + + + + + org.apache.maven.plugins + maven-surefire-plugin + 3.1.0 + + --add-opens java.base/java.lang=ALL-UNNAMED + + + + diff --git a/src/main/java/nl/jssl/jsontoy/serialize/JSONSerializer.java b/src/main/java/nl/jssl/jsontoy/serialize/JSONSerializer.java index 6cc95b3..fd6ee54 100644 --- a/src/main/java/nl/jssl/jsontoy/serialize/JSONSerializer.java +++ b/src/main/java/nl/jssl/jsontoy/serialize/JSONSerializer.java @@ -11,9 +11,9 @@ public abstract class JSONSerializer { if (object == null) { return ""; } else if (object instanceof Number || object instanceof Boolean) { - return "" + object.toString(); + return "" + object; } else if (object instanceof CharSequence || object instanceof Character) { - return "\"" + object.toString() + "\""; + return "\"" + object + "\""; } else { return handle(object); } diff --git a/src/main/java/nl/jssl/jsontoy/serialize/SynthSerializerFactory.java b/src/main/java/nl/jssl/jsontoy/serialize/SynthSerializerFactory.java index 5afc7b4..bf0daaf 100644 --- a/src/main/java/nl/jssl/jsontoy/serialize/SynthSerializerFactory.java +++ b/src/main/java/nl/jssl/jsontoy/serialize/SynthSerializerFactory.java @@ -1,5 +1,6 @@ package nl.jssl.jsontoy.serialize; +import java.lang.reflect.InvocationTargetException; import java.util.ArrayList; import java.util.Arrays; import java.util.HashMap; @@ -36,7 +37,7 @@ class SynthSerializerFactory { private static final String SET = "java.util.Set"; private static final String MAP = "java.util.Map"; - private static final Map> serializers = new HashMap<>(); + private static final Map, JSONSerializer> serializers = new HashMap<>(); private static final String ROOT_PACKAGE = "serializer."; private final ClassPool pool = ClassPool.getDefault(); @@ -55,37 +56,29 @@ class SynthSerializerFactory { } } - JSONSerializer createSerializer(Class beanjavaClass) { + @SuppressWarnings("unchecked") + JSONSerializer createSerializer(Class beanjavaClass) { + if (serializers.containsKey(beanjavaClass)) { + return (JSONSerializer) serializers.get(beanjavaClass); + } try { CtClass beanClass = pool.get(beanjavaClass.getName()); - - return createSerializer2(beanClass); - } catch (NotFoundException e) { + return tryCreateSerializer(beanjavaClass, beanClass); + } catch (NotFoundException | CannotCompileException | InstantiationException | IllegalAccessException | + InvocationTargetException | NoSuchMethodException e) { throw new SerializerCreationException(e); } } - @SuppressWarnings("unchecked") - private JSONSerializer createSerializer2(CtClass beanClass) { - if (serializers.containsKey(createSerializerName(beanClass))) { - return (JSONSerializer) serializers.get(createSerializerName(beanClass)); - } - try { - return tryCreateSerializer(beanClass); - } catch (NotFoundException | CannotCompileException | InstantiationException | IllegalAccessException e) { - throw new SerializerCreationException(e); - } - } - - private JSONSerializer tryCreateSerializer(CtClass beanClass) throws NotFoundException, CannotCompileException, InstantiationException, - IllegalAccessException { + private JSONSerializer tryCreateSerializer(Class javaClass, CtClass beanClass) throws NotFoundException, CannotCompileException, InstantiationException, + IllegalAccessException, InvocationTargetException, NoSuchMethodException { CtClass serializerClass = pool.makeClass(createSerializerName(beanClass), serializerBase); addToJsonStringMethod(beanClass, serializerClass); JSONSerializer jsonSerializer = createSerializerInstance(serializerClass); - serializers.put(createSerializerName(beanClass), jsonSerializer); + serializers.put(javaClass, jsonSerializer); return jsonSerializer; } @@ -99,16 +92,16 @@ class SynthSerializerFactory { /* * Creates the source, handling the for JSON different types of classes */ - private String createToJSONStringMethodSource(CtClass beanClass) throws NotFoundException { + private String createToJSONStringMethodSource(CtClass beanClass) throws NotFoundException { String source = "public String handle(Object object){\n"; if (beanClass.isArray()) { source += "\tObject[] array=(Object[])object;\n"; - source += handleArray(beanClass); + source += handleArray(); } else if (isCollection(beanClass)) { source += "\tObject[] array=((java.util.Collection)object).toArray();\n"; - source += handleArray(beanClass); + source += handleArray(); } else if (isMap(beanClass)) { - source += handleMap(beanClass); + source += handleMap(); } else if (!isPrimitiveOrWrapperOrString(beanClass)) { List getters = getGetters(beanClass); if (shouldAddGetterCallers(getters)) { @@ -117,18 +110,17 @@ class SynthSerializerFactory { } else { source += "\treturn \"\";}"; } - System.out.println(source); return source; } /* * Any Collection is converted to an array, after which code is generated to handle the single elements. - * + * * A subserializer is created for every single element, but most of the time it will be the same cached instance. - * + * * The generated code fills a StringBuilder. The values are generated by the subserializers */ - private String handleArray(CtClass beanClass) { + private String handleArray() { String source = "\tStringBuilder result=new StringBuilder(\"[\");\n"; source += "\tfor (int i=0; i getters) throws NotFoundException { + private String addGetterCallers(CtClass beanClass, String source, List getters) { int index = 0; source += "\treturn "; source += "\"{"; @@ -176,13 +168,13 @@ class SynthSerializerFactory { @SuppressWarnings("unchecked") private JSONSerializer createSerializerInstance(CtClass serializerClass) throws InstantiationException, IllegalAccessException, - CannotCompileException { - return (JSONSerializer) serializerClass.toClass().newInstance(); + CannotCompileException, NoSuchMethodException, InvocationTargetException { + return (JSONSerializer) serializerClass.toClass().getConstructor().newInstance(); } /* * custom root package is prepended to avoid the java.lang class in which it's illegal to create new classes - * + * * Array marks ( '[]' ) are replaced by the 'Array', Otherwise the SerializerClassName would be syntactically incorrect */ public String createSerializerName(CtClass beanClass) { @@ -218,7 +210,7 @@ class SynthSerializerFactory { /* * The JSON vernacular for key:value is pair... */ - private String addPair(CtClass classToSerialize, String source, CtMethod getter) throws NotFoundException { + private String addPair(CtClass classToSerialize, String source, CtMethod getter) { source += jsonKey(getter); source += ": "; // what is the rule when it comes to spaces in json? source += jsonValue(classToSerialize, getter); @@ -232,20 +224,14 @@ class SynthSerializerFactory { return "\\\"" + toFieldName(getter.getName()) + "\\\""; } - private String jsonValue(CtClass classToSerialize, CtMethod getter) throws NotFoundException { - String source = ""; - CtClass returnType = getter.getReturnType(); - + private String jsonValue(CtClass classToSerialize, CtMethod getter) { /* primitives are wrapped so the produced methods adhere to the JSONSerializer interface */ - source = createSubSerializerForReturnTypeAndAddInvocationToSource(classToSerialize, getter, source, returnType); - - return source; + return createSubSerializerForReturnTypeAndAddInvocationToSource(classToSerialize, getter); } - private String createSubSerializerForReturnTypeAndAddInvocationToSource(CtClass classToSerialize, CtMethod getter, String source, CtClass returnType) { + private String createSubSerializerForReturnTypeAndAddInvocationToSource(CtClass classToSerialize, CtMethod getter) { /* NB there does not seem to be auto(un))boxing nor generic types (or other jdk1.5 stuff) in javassist compileable code */ - - source += "\"+" + Serializer.class.getName() + ".toJSONString("; + String source = "\"+" + Serializer.class.getName() + ".toJSONString("; // cast because of lack of generics source += "(" + cast(regularClassname(classToSerialize.getName())) + "object)." + getter.getName() + "()"; @@ -331,22 +317,22 @@ class SynthSerializerFactory { String asPrimitive(String name) { switch (name) { - case "int": - return "I"; - case "byte": - return "B"; - case "float": - return "F"; - case "long": - return "J"; - case "boolean": - return "Z"; - case "char": - return "C"; - case "double": - return "D"; - case "short": - return "S"; + case "int": + return "I"; + case "byte": + return "B"; + case "float": + return "F"; + case "long": + return "J"; + case "boolean": + return "Z"; + case "char": + return "C"; + case "double": + return "D"; + case "short": + return "S"; } return ""; } diff --git a/src/test/java/nl/jssl/jsontoy/serialize/performance/Jackson.java b/src/test/java/nl/jssl/jsontoy/serialize/performance/JacksonComparisonTest.java similarity index 70% rename from src/test/java/nl/jssl/jsontoy/serialize/performance/Jackson.java rename to src/test/java/nl/jssl/jsontoy/serialize/performance/JacksonComparisonTest.java index 56a5336..8daaa5b 100644 --- a/src/test/java/nl/jssl/jsontoy/serialize/performance/Jackson.java +++ b/src/test/java/nl/jssl/jsontoy/serialize/performance/JacksonComparisonTest.java @@ -13,50 +13,49 @@ import org.junit.Test; import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.ObjectMapper; -public class Jackson { - List trashbin = new ArrayList(); +public class JacksonComparisonTest { + private static final int ITERATIONS = 20; + private static final int INNERLOOP_COUNT = 100000; + List trashbin = new ArrayList<>(); @Test - public void jackson() throws JsonProcessingException { + public void testPerformance() throws JsonProcessingException { + System.out.println("jackson,jsontoy"); ObjectMapper objectMapper = new ObjectMapper(); Bean1 bean1 = new Bean1(); Bean2 bean2 = new Bean2(); bean1.setData1(UUID.randomUUID().toString()); bean1.setBean2(bean2); bean2.setData2(UUID.randomUUID().toString()); - String valueAsString = objectMapper.writeValueAsString(bean1); - String jsonString = Serializer.toJSONString(bean1); + String valueAsString; + String jsonString; - for (int c = 0; c < 20; c++) { + for (int c = 0; c < ITERATIONS; c++) { trashbin.clear(); - System.gc(); long t0 = System.currentTimeMillis(); - for (int i = 0; i < 100000; i++) { + for (int i = 0; i < INNERLOOP_COUNT; i++) { bean1 = new Bean1(); bean2 = new Bean2(); bean1.setData1(UUID.randomUUID().toString()); bean1.setBean2(bean2); bean2.setData2(UUID.randomUUID().toString()); valueAsString = objectMapper.writeValueAsString(bean1); - // System.out.println(valueAsString); trashbin.add(valueAsString); } - System.out.print(System.currentTimeMillis() - t0); + System.out.printf("% 7d",(System.currentTimeMillis() - t0)); System.out.print(","); trashbin.clear(); - System.gc(); long tt0 = System.currentTimeMillis(); - for (int i = 0; i < 100000; i++) { + for (int i = 0; i < INNERLOOP_COUNT; i++) { bean1 = new Bean1(); bean2 = new Bean2(); bean1.setData1(UUID.randomUUID().toString()); bean1.setBean2(bean2); bean2.setData2(UUID.randomUUID().toString()); jsonString = Serializer.toJSONString(bean1); - // System.out.println(jsonString); trashbin.add(jsonString); } - System.out.println(System.currentTimeMillis() - tt0); + System.out.printf("% 7d%n",System.currentTimeMillis() - tt0); } }