From 0a02524d865ad37da078e89e53631095edc311b2 Mon Sep 17 00:00:00 2001 From: Shautvast Date: Sat, 15 Jul 2023 18:18:05 +0200 Subject: [PATCH] 1.7 generic array handling, updated escaping --- jmh/pom.xml | 4 +- .../json/jmh/ArrayReflectionBenchmarks.java | 79 +++++ .../sanderhautvast/json/jmh/Benchmarks.java | 10 +- lib/pom.xml | 4 +- .../nl/sanderhautvast/json/ser/Mapper.java | 278 +++++------------- pom.xml | 2 +- 6 files changed, 163 insertions(+), 214 deletions(-) create mode 100644 jmh/src/main/java/nl/sanderhautvast/json/jmh/ArrayReflectionBenchmarks.java diff --git a/jmh/pom.xml b/jmh/pom.xml index e9b6fd9..5514967 100644 --- a/jmh/pom.xml +++ b/jmh/pom.xml @@ -5,12 +5,12 @@ nl.sander jsonthingy-pom - 1.6 + 1.7 JsonToy-JMH jsonthingy-jmhtests - 1.6 + 1.7 jar diff --git a/jmh/src/main/java/nl/sanderhautvast/json/jmh/ArrayReflectionBenchmarks.java b/jmh/src/main/java/nl/sanderhautvast/json/jmh/ArrayReflectionBenchmarks.java new file mode 100644 index 0000000..206b0da --- /dev/null +++ b/jmh/src/main/java/nl/sanderhautvast/json/jmh/ArrayReflectionBenchmarks.java @@ -0,0 +1,79 @@ +package nl.sanderhautvast.json.jmh; + +import org.openjdk.jmh.annotations.*; + +import java.lang.reflect.Array; +import java.util.concurrent.TimeUnit; + +@State(Scope.Thread) +@BenchmarkMode(Mode.AverageTime) +@OutputTimeUnit(TimeUnit.NANOSECONDS) +public class ArrayReflectionBenchmarks { + + private static final int ITERATIONS = 10; + + public static void main(String[] args) { + new ArrayReflectionBenchmarks().testReflectiveArray(); + } + + @Benchmark + public void testReflectiveArray() { + int[] r1 = {1}; + int[] r2 = {1, 2}; + int[][] table1 = {r1, r2}; + int[][] table2 = {r1, r2}; + int[][][] schema = {table1, table2}; + for (int i = 0; i < ITERATIONS; i++) { + addArrayElements(schema); + } + } + + @Benchmark + public void testNonReflectiveArray() { + int[] r1 = {1}; + int[] r2 = {1, 2}; + int[][] table1 = {r1, r2}; + int[][] table2 = {r1, r2}; + int[][][] schema = {table1, table2}; + for (int i = 0; i < ITERATIONS; i++) { + addIntegerArray(schema); + } + } + + private int addArrayElements(Object o) { + int sum = 0; + if (o.getClass().isArray()) { + int length = Array.getLength(o); + for (int i = 0; i < length; i++) { + sum += addArrayElements(Array.get(o, i)); + } + } else { + sum += (Integer) o; + } + return sum; + } + + private int addIntegerArray(int[][][] o) { + int sum = 0; + for (int[][] ints : o) { + sum += addIntegerArray2(ints); + } + return sum; + } + + private int addIntegerArray2(int[][] o) { + int sum = 0; + for (int[] ints : o) { + sum += addIntegerArray3(ints); + } + return sum; + } + + private int addIntegerArray3(int[] o) { + int sum = 0; + for (int j : o) { + sum += j; + } + return sum; + } +} diff --git a/jmh/src/main/java/nl/sanderhautvast/json/jmh/Benchmarks.java b/jmh/src/main/java/nl/sanderhautvast/json/jmh/Benchmarks.java index 271cde1..16404e0 100644 --- a/jmh/src/main/java/nl/sanderhautvast/json/jmh/Benchmarks.java +++ b/jmh/src/main/java/nl/sanderhautvast/json/jmh/Benchmarks.java @@ -8,14 +8,14 @@ import org.openjdk.jmh.annotations.*; import java.util.UUID; import java.util.concurrent.TimeUnit; -@State(Scope.Thread) -@BenchmarkMode(Mode.AverageTime) -@OutputTimeUnit(TimeUnit.NANOSECONDS) +//@State(Scope.Thread) +//@BenchmarkMode(Mode.AverageTime) +//@OutputTimeUnit(TimeUnit.NANOSECONDS) public class Benchmarks { private static final int ITERATIONS = 10; - @Benchmark +// @Benchmark public void testJson() { Bean1 bean1; Bean2 bean2; @@ -30,7 +30,7 @@ public class Benchmarks { } } - @Benchmark +// @Benchmark public void testJackson() throws JsonProcessingException { Bean1 bean1; Bean2 bean2; diff --git a/lib/pom.xml b/lib/pom.xml index 1aa7131..2e3c888 100644 --- a/lib/pom.xml +++ b/lib/pom.xml @@ -5,12 +5,12 @@ nl.sander jsonthingy-pom - 1.6 + 1.7 JsonToy jsonthingy - 1.6 + 1.7 jar diff --git a/lib/src/main/java/nl/sanderhautvast/json/ser/Mapper.java b/lib/src/main/java/nl/sanderhautvast/json/ser/Mapper.java index 96ae3a4..477c10e 100644 --- a/lib/src/main/java/nl/sanderhautvast/json/ser/Mapper.java +++ b/lib/src/main/java/nl/sanderhautvast/json/ser/Mapper.java @@ -4,6 +4,7 @@ import org.objectweb.asm.ClassReader; import org.objectweb.asm.ClassWriter; import java.io.IOException; +import java.lang.reflect.Array; import java.lang.reflect.InvocationTargetException; import java.math.BigDecimal; import java.math.BigInteger; @@ -20,6 +21,15 @@ public class Mapper { private static final Map, BaseMapper> mappers = new ConcurrentHashMap<>(); private static final ByteClassLoader generatedClassesLoader = new ByteClassLoader(); + public static final char[] TAB = {'\\', 't'}; + public static final char[] DOUBLEQUOTE = {'\\', '\"'}; + public static final char[] SLASH = {'\\', '/'}; + public static final char[] RETURN = {'\\', 'r'}; + public static final char[] BACKSLASH = {'\\', '\\'}; + public static final char[] NEWLINE = {'\\', 'n'}; + public static final char[] BELL = {'\\', 'b'}; + public static final char[] FORMFEED = {'\\', 'f'}; + private static final char[][] MAP = createEscapeMap(); /** @@ -49,25 +59,7 @@ public class Mapper { } else { Class type = value.getClass(); if (type.isArray()) { - if (value instanceof byte[]) { - array(b, (byte[]) value); - } else if (value instanceof int[]) { - array(b, (int[]) value); - } else if (value instanceof short[]) { - array(b, (short[]) value); - } else if (value instanceof boolean[]) { - array(b, (boolean[]) value); - } else if (value instanceof char[]) { - array(b, (char[]) value); - } else if (value instanceof long[]) { - array(b, (long[]) value); - } else if (value instanceof float[]) { - array(b, (float[]) value); - } else if (value instanceof double[]) { - array(b, (double[]) value); - } else { - array(b, (Object[]) value); - } + array(b, value); } else if (value instanceof Collection) { list(b, (Collection) value); } else if (value instanceof Map) { @@ -104,140 +96,16 @@ public class Mapper { } } - private static void array(StringBuilder b, Object[] array) { - if (array.length == 0) { - b.append("[]"); - } else { - Object first = array[0]; - b.append("["); - Mapper.json(b, first); - Arrays.stream(array).skip(1) - .forEach(element -> { - b.append(","); - Mapper.json(b, element); - }); - b.append("]"); - } - } - - private static void array(StringBuilder b, byte[] array) { - if (array.length == 0) { - b.append("[]"); - } else { - byte first = array[0]; - b.append("["); - json(b, first); - for (int i = 1; i < array.length; i++) { - b.append(","); - json(b, array[i]); - } - b.append("]"); - } - } - - private static void array(StringBuilder b, short[] array) { - if (array.length == 0) { - b.append("[]"); - } else { - short first = array[0]; - b.append("["); - json(b, first); - for (int i = 1; i < array.length; i++) { - b.append(","); - json(b, array[i]); - } - b.append("]"); - } - } - - private static void array(StringBuilder b, long[] array) { - if (array.length == 0) { - b.append("[]"); - } else { - long first = array[0]; - b.append("["); - json(b, first); - for (int i = 1; i < array.length; i++) { - b.append(","); - json(b, array[i]); - } - b.append("]"); - } - } - - private static void array(StringBuilder b, boolean[] array) { - if (array.length == 0) { - b.append("[]"); - } else { - boolean first = array[0]; - b.append("["); - json(b, first); - for (int i = 1; i < array.length; i++) { - b.append(","); - json(b, array[i]); - } - b.append("]"); - } - } - - private static void array(StringBuilder b, double[] array) { - if (array.length == 0) { - b.append("[]"); - } else { - double first = array[0]; - b.append("["); - json(b, first); - for (int i = 1; i < array.length; i++) { - b.append(","); - json(b, array[i]); - } - b.append("]"); - } - } - - private static void array(StringBuilder b, char[] array) { - if (array.length == 0) { - b.append("[]"); - } else { - char first = array[0]; - b.append("["); - json(b, first); - for (int i = 1; i < array.length; i++) { - b.append(","); - json(b, array[i]); - } - b.append("]"); - } - } - - private static void array(StringBuilder b, float[] array) { - if (array.length == 0) { - b.append("[]"); - } else { - float first = array[0]; - b.append("["); - json(b, first); - for (int i = 1; i < array.length; i++) { - b.append(","); - json(b, array[i]); - } - b.append("]"); - } - } - - private static void array(StringBuilder b, int[] array) { - if (array.length == 0) { - b.append("[]"); - } else { - int first = array[0]; - b.append("["); - json(b, first); - for (int i = 1; i < array.length; i++) { - b.append(","); - json(b, array[i]); - } - b.append("]"); + //TODO make this more performant + private static void array(StringBuilder b, Object value) { + b.append("["); + StringJoiner joiner = new StringJoiner(","); + for (int i = 0; i < Array.getLength(value); i++) { + Object arrayElement = Array.get(value, i); + joiner.add(Mapper.json(arrayElement)); // recursie } + b.append(joiner); + b.append("]"); } @SuppressWarnings({"rawtypes", "unchecked"}) @@ -342,61 +210,63 @@ public class Mapper { escape(b, String.valueOf(c)); } - static void escape(StringBuilder b, String value) { - int offset = b.length(); - b.append(value); - int i = offset; - while (i < b.length()) { - char c = b.charAt(i); - switch (c) { - case '\t': - b.replace(i, i + 1, "\\"); - b.insert(i + 1, "t"); - break; - case '\"': - b.replace(i, i + 1, "\\"); - b.insert(++i, "\""); - break; - case '/': - b.replace(i, i + 1, "\\"); - b.insert(++i, "/"); - break; - case '\r': - b.replace(i, i + 1, "\\"); - b.insert(++i, "r"); - break; - case '\n': - b.replace(i, i + 1, "\\"); - b.insert(++i, "n"); - break; - case '\b': - b.replace(i, i + 1, "\\"); - b.insert(++i, "b"); - break; - case '\f': - b.replace(i, i + 1, "\\"); - b.insert(++i, "f"); - break; - case '\\': - b.replace(i, i + 1, "\\"); - b.insert(++i, "\\"); - break; - case '\'': - break; - default: - if ((c <= '\u001F') || (c >= '\u007F' && c <= '\u009F') || (c >= '\u2000' && c <= '\u20FF')) { - String ss = Integer.toHexString(c); - b.replace(i, i + 1, "\\"); - b.insert(++i, "u"); - for (int k = 0; k < 4 - ss.length(); k++) { - b.insert(++i, '0'); - } - b.insert(++i, ss.toUpperCase()); - } + private static char[][] createEscapeMap() { + char[][] charmap = new char[0x2100][]; + for (int i = 0; i < 0x2100; i++) { + char c = (char) i; + char[] replacement; + if (c == '\t') { + replacement = TAB; + } else if (c == '\"') { + replacement = DOUBLEQUOTE; + } else if (c == '/') { + replacement = SLASH; + } else if (c == '\r') { + replacement = RETURN; + } else if (c == '\\') { + replacement = BACKSLASH; + } else if (c == '\n') { + replacement = NEWLINE; + } else if (c == '\b') { + replacement = BELL; + } else if (c == '\f') { + replacement = FORMFEED; + } else if ((c <= '\u001F') || (c >= '\u007F' && c <= '\u009F') || (c >= '\u2000')) { + replacement = new char[6]; + replacement[0] = '\\'; + replacement[1] = 'u'; + + String hex = Integer.toHexString(c).toUpperCase(); + int hexlen = hex.length(); + for (int k = 0; k < 4 - hexlen; k++) { + replacement[k + 2] = '0'; + } + for (int k = 0; k < hexlen; k++) { + replacement[6 - hexlen + k] = hex.charAt(k); + } + } else { + replacement = new char[]{c}; + } + charmap[i] = replacement; + } + + return charmap; + } + + //both methods are equally slow it seems + //mainly because we can't do a batch copy into the stringbuilder and have to map every character individually + static void escape(StringBuilder b, String value) { + for (int i = 0; i < value.length(); i++) { + int c = value.charAt(i); + + if (c < 0x2100) { + b.append(MAP[c]); + } else { + b.append((char) c); } - i++; } } + } diff --git a/pom.xml b/pom.xml index 0f58cee..1794eb3 100644 --- a/pom.xml +++ b/pom.xml @@ -5,7 +5,7 @@ JsonToy nl.sander jsonthingy-pom - 1.6 + 1.7 pom