You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@johnzon.apache.org by st...@apache.org on 2018/07/13 09:53:28 UTC

johnzon git commit: JOHNZON-179 improve writeArray performance

Repository: johnzon
Updated Branches:
  refs/heads/master a2cbd4375 -> f784faf2c


JOHNZON-179 improve writeArray performance

Array.get is really slow, so I rewrote the array handling
to render native types separately.


Project: http://git-wip-us.apache.org/repos/asf/johnzon/repo
Commit: http://git-wip-us.apache.org/repos/asf/johnzon/commit/f784faf2
Tree: http://git-wip-us.apache.org/repos/asf/johnzon/tree/f784faf2
Diff: http://git-wip-us.apache.org/repos/asf/johnzon/diff/f784faf2

Branch: refs/heads/master
Commit: f784faf2ce2c954bbf6168e476d9152a3843b50f
Parents: a2cbd43
Author: Mark Struberg <st...@apache.org>
Authored: Fri Jul 13 11:52:19 2018 +0200
Committer: Mark Struberg <st...@apache.org>
Committed: Fri Jul 13 11:52:19 2018 +0200

----------------------------------------------------------------------
 .../johnzon/mapper/MappingGeneratorImpl.java    | 153 ++++++++++++++-----
 .../johnzon/mapper/MapperPerformanceTest.java   | 122 +++++++++++++++
 .../org/apache/johnzon/mapper/MapperTest.java   |  38 ++++-
 3 files changed, 276 insertions(+), 37 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/johnzon/blob/f784faf2/johnzon-mapper/src/main/java/org/apache/johnzon/mapper/MappingGeneratorImpl.java
----------------------------------------------------------------------
diff --git a/johnzon-mapper/src/main/java/org/apache/johnzon/mapper/MappingGeneratorImpl.java b/johnzon-mapper/src/main/java/org/apache/johnzon/mapper/MappingGeneratorImpl.java
index 3fa054a..b4b9cef 100644
--- a/johnzon-mapper/src/main/java/org/apache/johnzon/mapper/MappingGeneratorImpl.java
+++ b/johnzon-mapper/src/main/java/org/apache/johnzon/mapper/MappingGeneratorImpl.java
@@ -322,32 +322,7 @@ public class MappingGeneratorImpl implements MappingGenerator {
             return;
         }
         if (array || (dynamic && type.isArray())) {
-            final int length = Array.getLength(value);
-            if (length == 0 && config.isSkipEmptyArray()) {
-                return;
-            }
-
-            if(config.isTreatByteArrayAsBase64() && (type == byte[].class /*|| type == Byte[].class*/)) {
-                String base64EncodedByteArray = Base64.getEncoder().encodeToString((byte[]) value);
-                generator.write(key, base64EncodedByteArray);
-                return;
-            }
-            if(config.isTreatByteArrayAsBase64URL() && (type == byte[].class /*|| type == Byte[].class*/)) {
-                generator.write(key, Base64.getUrlEncoder().encodeToString((byte[]) value));
-                return;
-            }
-
-            generator.writeStartArray(key);
-            for (int i = 0; i < length; i++) {
-                final Object o = Array.get(value, i);
-                String valJsonPointer = jsonPointers.get(o);
-                if (valJsonPointer != null) {
-                    writePrimitives(valJsonPointer);
-                } else {
-                    writeItem(itemConverter != null ? itemConverter.from(o) : o, ignoredProperties, isDeduplicateObjects ? new JsonPointerTracker(jsonPointer, i) : null);
-                }
-            }
-            generator.writeEnd();
+            writeArray(type, itemConverter, key, value, ignoredProperties, jsonPointer);
         } else if (collection || (dynamic && Collection.class.isAssignableFrom(type))) {
             generator.writeStartArray(key);
             int i = 0;
@@ -411,6 +386,121 @@ public class MappingGeneratorImpl implements MappingGenerator {
         }
     }
 
+    /**
+     * Write a JSON Array with a given Array Value, like byte[], int[], Person[] etc.
+     * @param key either the attribute key or {@code null} if the array should be rendered without key
+     */
+    private void writeArray(Class<?> type, Adapter itemConverter, String key, Object arrayValue, Collection<String> ignoredProperties, JsonPointerTracker jsonPointer) {
+        final int length = Array.getLength(arrayValue);
+        if (length == 0 && config.isSkipEmptyArray()) {
+            return;
+        }
+
+        if(config.isTreatByteArrayAsBase64() && (type == byte[].class /*|| type == Byte[].class*/)) {
+            String base64EncodedByteArray = Base64.getEncoder().encodeToString((byte[]) arrayValue);
+            if (key != null) {
+                generator.write(key, base64EncodedByteArray);
+            } else {
+                generator.write(base64EncodedByteArray);
+            }
+            return;
+        }
+        if(config.isTreatByteArrayAsBase64URL() && (type == byte[].class /*|| type == Byte[].class*/)) {
+            if (key != null) {
+                generator.write(key, Base64.getUrlEncoder().encodeToString((byte[]) arrayValue));
+            } else {
+                generator.write(Base64.getUrlEncoder().encodeToString((byte[]) arrayValue));
+            }
+            return;
+        }
+
+        if (key != null) {
+            generator.writeStartArray(key);
+        } else {
+            generator.writeStartArray();
+        }
+
+        // some specialised arrays to speed up conversion.
+        // Needed since Array.get is rather slow :(
+        if (type == byte[].class) {
+            byte[] tArrayValue = (byte[]) arrayValue;
+            for (int i = 0; i < length; i++) {
+                final byte o = tArrayValue[i];
+                generator.write(o);
+            }
+        } else if (type == short[].class) {
+            short[] tArrayValue = (short[]) arrayValue;
+            for (int i = 0; i < length; i++) {
+                final short o = tArrayValue[i];
+                generator.write(o);
+            }
+        } else if (type == int[].class) {
+            int[] tArrayValue = (int[]) arrayValue;
+            for (int i = 0; i < length; i++) {
+                final int o = tArrayValue[i];
+                generator.write(o);
+            }
+        } else if (type == long[].class) {
+            long[] tArrayValue = (long[]) arrayValue;
+            for (int i = 0; i < length; i++) {
+                final long o = tArrayValue[i];
+                generator.write(o);
+            }
+        } else if (type == float[].class) {
+            float[] tArrayValue = (float[]) arrayValue;
+            for (int i = 0; i < length; i++) {
+                final float o = tArrayValue[i];
+                generator.write(o);
+            }
+        } else if (type == double[].class) {
+            double[] tArrayValue = (double[]) arrayValue;
+            for (int i = 0; i < length; i++) {
+                final double o = tArrayValue[i];
+                generator.write(o);
+            }
+        } else if (type == char[].class) {
+            char[] tArrayValue = (char[]) arrayValue;
+            for (int i = 0; i < length; i++) {
+                final char o = tArrayValue[i];
+                generator.write(String.valueOf(o));
+            }
+        } else if (type == boolean[].class) {
+            boolean[] tArrayValue = (boolean[]) arrayValue;
+            for (int i = 0; i < length; i++) {
+                final boolean o = tArrayValue[i];
+                generator.write(o);
+            }
+        } else if (type == Byte[].class ||
+                   type == Short[].class ||
+                   type == Integer[].class ||
+                   type == Long[].class ||
+                   type == Float[].class ||
+                   type == Double[].class ||
+                   type == Character[].class ||
+                   type == Boolean[].class) {
+            // Wrapper types do not not need deduplication
+            Object[] oArrayValue = (Object[]) arrayValue;
+            for (int i = 0; i < length; i++) {
+                final Object o = oArrayValue[i];
+                writeItem(itemConverter != null ? itemConverter.from(o) : o, ignoredProperties, null);
+            }
+        } else {
+            // must be object arrays
+            for (int i = 0; i < length; i++) {
+                Object[] oArrayValue = (Object[]) arrayValue;
+                final Object o = oArrayValue[i];
+                String valJsonPointer = jsonPointers.get(o);
+                if (valJsonPointer != null) {
+                    // write the JsonPointer as String natively
+                    generator.write(valJsonPointer);
+                } else {
+                    writeItem(itemConverter != null ? itemConverter.from(o) : o, ignoredProperties, isDeduplicateObjects ? new JsonPointerTracker(jsonPointer, i) : null);
+                }
+            }
+        }
+        generator.writeEnd();
+    }
+
     private void writeItem(final Object o, final Collection<String> ignoredProperties, JsonPointerTracker jsonPointer) {
         if (o == null) {
             generator.writeNull();
@@ -420,16 +510,7 @@ public class MappingGeneratorImpl implements MappingGenerator {
             } else if (o.getClass().isArray()) {
                 final int length = Array.getLength(o);
                 if (length > 0 || !config.isSkipEmptyArray()) {
-                    generator.writeStartArray();
-                    for (int i = 0; i < length; i++) {
-                        Object t = Array.get(o, i);
-                        if (t == null) {
-                            generator.writeNull();
-                        } else {
-                            writeItem(t, ignoredProperties, isDeduplicateObjects ? new JsonPointerTracker(jsonPointer, i) : null);
-                        }
-                    }
-                    generator.writeEnd();
+                    writeArray(o.getClass(), null, null, o, ignoredProperties, jsonPointer);
                 }
             } else {
                 String valJsonPointer = jsonPointers.get(o);

http://git-wip-us.apache.org/repos/asf/johnzon/blob/f784faf2/johnzon-mapper/src/test/java/org/apache/johnzon/mapper/MapperPerformanceTest.java
----------------------------------------------------------------------
diff --git a/johnzon-mapper/src/test/java/org/apache/johnzon/mapper/MapperPerformanceTest.java b/johnzon-mapper/src/test/java/org/apache/johnzon/mapper/MapperPerformanceTest.java
new file mode 100644
index 0000000..34e9aee
--- /dev/null
+++ b/johnzon-mapper/src/test/java/org/apache/johnzon/mapper/MapperPerformanceTest.java
@@ -0,0 +1,122 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.johnzon.mapper;
+
+import org.junit.Ignore;
+import org.junit.Test;
+
+import java.io.StringWriter;
+import java.util.Arrays;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * This test is usually being executed manually.
+ * It contains a few performance related tests and is intended for profiling etc.
+ */
+@Ignore // intended to be run manually from an IDE for example.
+public class MapperPerformanceTest {
+
+    public static final int ARRAY_SIZE = 60_000_000;
+
+    @Test
+   public void byteArrayBase64WriteTest() {
+
+       Mapper mapper = new MapperBuilder().setTreatByteArrayAsBase64(false).build();
+
+       SomeDocument doc = createTestDocument();
+
+       StringWriter writer = new StringWriter();
+       mapper.writeObject(doc, writer);
+
+       long start = System.nanoTime();
+
+       for (int i=0; i< 10; i++) {
+           writer = new StringWriter();
+           mapper.writeObject(doc, writer);
+       }
+       long end = System.nanoTime();
+
+       System.out.println("took: " + TimeUnit.NANOSECONDS.toMillis(end-start) + " ms");
+   }
+
+    @Test
+   public void intArrayWriteTest() {
+
+       Mapper mapper = new MapperBuilder().setTreatByteArrayAsBase64(false).build();
+
+       SomeIntDocument doc = createTestIntDocument();
+
+       StringWriter writer = new StringWriter();
+       mapper.writeObject(doc, writer);
+
+       long start = System.nanoTime();
+
+       for (int i=0; i< 10; i++) {
+           writer = new StringWriter();
+           mapper.writeObject(doc, writer);
+       }
+       long end = System.nanoTime();
+
+       System.out.println("took: " + TimeUnit.NANOSECONDS.toMillis(end-start) + " ms");
+   }
+
+    private SomeDocument createTestDocument() {
+        byte[] content = new byte[ARRAY_SIZE];
+        Arrays.fill(content, (byte)'x');
+
+        SomeDocument doc = new SomeDocument();
+        doc.setContent(content);
+        return doc;
+    }
+
+    private SomeIntDocument createTestIntDocument() {
+        Integer[] content = new Integer[ARRAY_SIZE];
+        Arrays.fill(content, (int)'x');
+
+        SomeIntDocument doc = new SomeIntDocument();
+        doc.setContent(content);
+        return doc;
+    }
+
+
+    public static class SomeDocument {
+       private byte[] content;
+
+       public byte[] getContent() {
+           return content;
+       }
+
+       public void setContent(byte[] content) {
+           this.content = content;
+       }
+   }
+
+    public static class SomeIntDocument {
+       private Integer[] content;
+
+        public Integer[] getContent() {
+            return content;
+        }
+
+        public void setContent(Integer[] content) {
+            this.content = content;
+        }
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/johnzon/blob/f784faf2/johnzon-mapper/src/test/java/org/apache/johnzon/mapper/MapperTest.java
----------------------------------------------------------------------
diff --git a/johnzon-mapper/src/test/java/org/apache/johnzon/mapper/MapperTest.java b/johnzon-mapper/src/test/java/org/apache/johnzon/mapper/MapperTest.java
index 3582460..8c8c4ce 100644
--- a/johnzon-mapper/src/test/java/org/apache/johnzon/mapper/MapperTest.java
+++ b/johnzon-mapper/src/test/java/org/apache/johnzon/mapper/MapperTest.java
@@ -484,11 +484,47 @@ public class MapperTest {
 
     @Test
     public void writeArray() {
-        // integer
         ByteArrayOutputStream baos = new ByteArrayOutputStream();
+
+        baos.reset();
+        new MapperBuilder().build().writeArray(new Byte[] { 1, 2 }, baos);
+        assertEquals("[1,2]", new String(baos.toByteArray()));
+
+        baos.reset();
+        new MapperBuilder().build().writeArray(new Short[] { 1, 2 }, baos);
+        assertEquals("[1,2]", new String(baos.toByteArray()));
+
+        baos.reset();
+        baos = new ByteArrayOutputStream();
         new MapperBuilder().build().writeArray(new Integer[] { 1, 2 }, baos);
         assertEquals("[1,2]", new String(baos.toByteArray()));
 
+        baos.reset();
+        baos = new ByteArrayOutputStream();
+        new MapperBuilder().build().writeArray(new Long[] { 1L, 2L }, baos);
+        assertEquals("[1,2]", new String(baos.toByteArray()));
+
+        baos.reset();
+        baos = new ByteArrayOutputStream();
+        new MapperBuilder().build().writeArray(new Float[] { 1f, 2f }, baos);
+        assertEquals("[1.0,2.0]", new String(baos.toByteArray()));
+
+        baos.reset();
+        baos = new ByteArrayOutputStream();
+        new MapperBuilder().build().writeArray(new Double[] { 1d, 2d }, baos);
+        assertEquals("[1.0,2.0]", new String(baos.toByteArray()));
+
+        baos.reset();
+        baos = new ByteArrayOutputStream();
+        new MapperBuilder().build().writeArray(new Character[] { 'a', 'b' }, baos);
+        assertEquals("[\"a\",\"b\"]", new String(baos.toByteArray()));
+
+        baos.reset();
+        baos = new ByteArrayOutputStream();
+        new MapperBuilder().build().writeArray(new Boolean[] { true, false }, baos);
+        assertEquals("[true,false]", new String(baos.toByteArray()));
+
+
         // object
         baos = new ByteArrayOutputStream();
         new MapperBuilder().build().writeArray(new Pair[] { new Pair(1, "a"), new Pair(2, "b") }, baos);