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 <[email protected]> Authored: Fri Jul 13 11:52:19 2018 +0200 Committer: Mark Struberg <[email protected]> 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);
