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);