8247532: Records deserialization is slow
8248135: Build microbenchmarks with --enable-preview Test contributed by Chris Hegarty <chris.hegarty@oracle.com> Reviewed-by: chegar, psandoz, redestad, ihse
This commit is contained in:
parent
4bcd70acc0
commit
2f09989ec0
@ -90,10 +90,11 @@ $(eval $(call SetupJavaCompilation, BUILD_JDK_MICROBENCHMARK, \
|
||||
TARGET_RELEASE := $(TARGET_RELEASE_NEWJDK_UPGRADED), \
|
||||
SMALL_JAVA := false, \
|
||||
CLASSPATH := $(MICROBENCHMARK_CLASSPATH), \
|
||||
DISABLED_WARNINGS := processing rawtypes cast serial, \
|
||||
DISABLED_WARNINGS := processing rawtypes cast serial preview, \
|
||||
SRC := $(MICROBENCHMARK_SRC), \
|
||||
BIN := $(MICROBENCHMARK_CLASSES), \
|
||||
JAVA_FLAGS := --add-modules jdk.unsupported --limit-modules java.management, \
|
||||
JAVAC_FLAGS := --enable-preview, \
|
||||
))
|
||||
|
||||
$(BUILD_JDK_MICROBENCHMARK): $(JMH_COMPILE_JARS)
|
||||
|
@ -2182,7 +2182,7 @@ public class ObjectInputStream
|
||||
handles.markException(passHandle, resolveEx);
|
||||
}
|
||||
|
||||
final boolean isRecord = cl != null && isRecord(cl) ? true : false;
|
||||
final boolean isRecord = cl != null && isRecord(cl);
|
||||
if (isRecord) {
|
||||
assert obj == null;
|
||||
obj = readRecord(desc);
|
||||
@ -2289,14 +2289,14 @@ public class ObjectInputStream
|
||||
|
||||
FieldValues fieldValues = defaultReadFields(null, desc);
|
||||
|
||||
// retrieve the canonical constructor
|
||||
MethodHandle ctrMH = desc.getRecordConstructor();
|
||||
|
||||
// bind the stream field values
|
||||
ctrMH = RecordSupport.bindCtrValues(ctrMH, desc, fieldValues);
|
||||
// get canonical record constructor adapted to take two arguments:
|
||||
// - byte[] primValues
|
||||
// - Object[] objValues
|
||||
// and return Object
|
||||
MethodHandle ctrMH = RecordSupport.deserializationCtr(desc);
|
||||
|
||||
try {
|
||||
return ctrMH.invoke();
|
||||
return (Object) ctrMH.invokeExact(fieldValues.primValues, fieldValues.objValues);
|
||||
} catch (Exception e) {
|
||||
InvalidObjectException ioe = new InvalidObjectException(e.getMessage());
|
||||
ioe.initCause(e);
|
||||
|
@ -27,6 +27,7 @@ package java.io;
|
||||
|
||||
import java.lang.invoke.MethodHandle;
|
||||
import java.lang.invoke.MethodHandles;
|
||||
import java.lang.invoke.MethodType;
|
||||
import java.lang.ref.Reference;
|
||||
import java.lang.ref.ReferenceQueue;
|
||||
import java.lang.ref.SoftReference;
|
||||
@ -55,6 +56,7 @@ import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.Comparator;
|
||||
import java.util.HashSet;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.concurrent.ConcurrentMap;
|
||||
@ -191,8 +193,14 @@ public class ObjectStreamClass implements Serializable {
|
||||
|
||||
/** serialization-appropriate constructor, or null if none */
|
||||
private Constructor<?> cons;
|
||||
/** record canonical constructor, or null */
|
||||
/** record canonical constructor (shared among OSCs for same class), or null */
|
||||
private MethodHandle canonicalCtr;
|
||||
/** cache of record deserialization constructors per unique set of stream fields
|
||||
* (shared among OSCs for same class), or null */
|
||||
private DeserializationConstructorsCache deserializationCtrs;
|
||||
/** session-cache of record deserialization constructor
|
||||
* (in de-serialized OSC only), or null */
|
||||
private MethodHandle deserializationCtr;
|
||||
/** protection domains that need to be checked when calling the constructor */
|
||||
private ProtectionDomain[] domains;
|
||||
|
||||
@ -525,6 +533,7 @@ public class ObjectStreamClass implements Serializable {
|
||||
|
||||
if (isRecord) {
|
||||
canonicalCtr = canonicalRecordCtr(cl);
|
||||
deserializationCtrs = new DeserializationConstructorsCache();
|
||||
} else if (externalizable) {
|
||||
cons = getExternalizableConstructor(cl);
|
||||
} else {
|
||||
@ -740,7 +749,10 @@ public class ObjectStreamClass implements Serializable {
|
||||
this.cl = cl;
|
||||
if (cl != null) {
|
||||
this.isRecord = isRecord(cl);
|
||||
// canonical record constructor is shared
|
||||
this.canonicalCtr = osc.canonicalCtr;
|
||||
// cache of deserialization constructors is shared
|
||||
this.deserializationCtrs = osc.deserializationCtrs;
|
||||
}
|
||||
this.resolveEx = resolveEx;
|
||||
this.superDesc = superDesc;
|
||||
@ -2528,14 +2540,135 @@ public class ObjectStreamClass implements Serializable {
|
||||
}
|
||||
}
|
||||
|
||||
// a LRA cache of record deserialization constructors
|
||||
@SuppressWarnings("serial")
|
||||
private static final class DeserializationConstructorsCache
|
||||
extends ConcurrentHashMap<DeserializationConstructorsCache.Key, MethodHandle> {
|
||||
|
||||
// keep max. 10 cached entries - when the 11th element is inserted the oldest
|
||||
// is removed and 10 remains - 11 is the biggest map size where internal
|
||||
// table of 16 elements is sufficient (inserting 12th element would resize it to 32)
|
||||
private static final int MAX_SIZE = 10;
|
||||
private Key.Impl first, last; // first and last in FIFO queue
|
||||
|
||||
DeserializationConstructorsCache() {
|
||||
// start small - if there is more than one shape of ObjectStreamClass
|
||||
// deserialized, there will typically be two (current version and previous version)
|
||||
super(2);
|
||||
}
|
||||
|
||||
MethodHandle get(ObjectStreamField[] fields) {
|
||||
return get(new Key.Lookup(fields));
|
||||
}
|
||||
|
||||
synchronized MethodHandle putIfAbsentAndGet(ObjectStreamField[] fields, MethodHandle mh) {
|
||||
Key.Impl key = new Key.Impl(fields);
|
||||
var oldMh = putIfAbsent(key, mh);
|
||||
if (oldMh != null) return oldMh;
|
||||
// else we did insert new entry -> link the new key as last
|
||||
if (last == null) {
|
||||
last = first = key;
|
||||
} else {
|
||||
last = (last.next = key);
|
||||
}
|
||||
// may need to remove first
|
||||
if (size() > MAX_SIZE) {
|
||||
assert first != null;
|
||||
remove(first);
|
||||
first = first.next;
|
||||
if (first == null) {
|
||||
last = null;
|
||||
}
|
||||
}
|
||||
return mh;
|
||||
}
|
||||
|
||||
// a key composed of ObjectStreamField[] names and types
|
||||
static abstract class Key {
|
||||
abstract int length();
|
||||
abstract String fieldName(int i);
|
||||
abstract Class<?> fieldType(int i);
|
||||
|
||||
@Override
|
||||
public final int hashCode() {
|
||||
int n = length();
|
||||
int h = 0;
|
||||
for (int i = 0; i < n; i++) h = h * 31 + fieldType(i).hashCode();
|
||||
for (int i = 0; i < n; i++) h = h * 31 + fieldName(i).hashCode();
|
||||
return h;
|
||||
}
|
||||
|
||||
@Override
|
||||
public final boolean equals(Object obj) {
|
||||
if (!(obj instanceof Key)) return false;
|
||||
Key other = (Key) obj;
|
||||
int n = length();
|
||||
if (n != other.length()) return false;
|
||||
for (int i = 0; i < n; i++) if (fieldType(i) != other.fieldType(i)) return false;
|
||||
for (int i = 0; i < n; i++) if (!fieldName(i).equals(other.fieldName(i))) return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
// lookup key - just wraps ObjectStreamField[]
|
||||
static final class Lookup extends Key {
|
||||
final ObjectStreamField[] fields;
|
||||
|
||||
Lookup(ObjectStreamField[] fields) { this.fields = fields; }
|
||||
|
||||
@Override
|
||||
int length() { return fields.length; }
|
||||
|
||||
@Override
|
||||
String fieldName(int i) { return fields[i].getName(); }
|
||||
|
||||
@Override
|
||||
Class<?> fieldType(int i) { return fields[i].getType(); }
|
||||
}
|
||||
|
||||
// real key - copies field names and types and forms FIFO queue in cache
|
||||
static final class Impl extends Key {
|
||||
Impl next;
|
||||
final String[] fieldNames;
|
||||
final Class<?>[] fieldTypes;
|
||||
|
||||
Impl(ObjectStreamField[] fields) {
|
||||
this.fieldNames = new String[fields.length];
|
||||
this.fieldTypes = new Class<?>[fields.length];
|
||||
for (int i = 0; i < fields.length; i++) {
|
||||
fieldNames[i] = fields[i].getName();
|
||||
fieldTypes[i] = fields[i].getType();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
int length() { return fieldNames.length; }
|
||||
|
||||
@Override
|
||||
String fieldName(int i) { return fieldNames[i]; }
|
||||
|
||||
@Override
|
||||
Class<?> fieldType(int i) { return fieldTypes[i]; }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/** Record specific support for retrieving and binding stream field values. */
|
||||
static final class RecordSupport {
|
||||
|
||||
/** Binds the given stream field values to the given method handle. */
|
||||
/**
|
||||
* Returns canonical record constructor adapted to take two arguments:
|
||||
* {@code (byte[] primValues, Object[] objValues)}
|
||||
* and return
|
||||
* {@code Object}
|
||||
*/
|
||||
@SuppressWarnings("preview")
|
||||
static MethodHandle bindCtrValues(MethodHandle ctrMH,
|
||||
ObjectStreamClass desc,
|
||||
ObjectInputStream.FieldValues fieldValues) {
|
||||
static MethodHandle deserializationCtr(ObjectStreamClass desc) {
|
||||
// check the cached value 1st
|
||||
MethodHandle mh = desc.deserializationCtr;
|
||||
if (mh != null) return mh;
|
||||
mh = desc.deserializationCtrs.get(desc.getFields(false));
|
||||
if (mh != null) return desc.deserializationCtr = mh;
|
||||
|
||||
// retrieve record components
|
||||
RecordComponent[] recordComponents;
|
||||
try {
|
||||
Class<?> cls = desc.forClass();
|
||||
@ -2545,15 +2678,36 @@ public class ObjectStreamClass implements Serializable {
|
||||
throw new InternalError(e.getCause());
|
||||
}
|
||||
|
||||
Object[] args = new Object[recordComponents.length];
|
||||
for (int i = 0; i < recordComponents.length; i++) {
|
||||
String name = recordComponents[i].getName();
|
||||
Class<?> type= recordComponents[i].getType();
|
||||
Object o = streamFieldValue(name, type, desc, fieldValues);
|
||||
args[i] = o;
|
||||
}
|
||||
// retrieve the canonical constructor
|
||||
// (T1, T2, ..., Tn):TR
|
||||
mh = desc.getRecordConstructor();
|
||||
|
||||
return MethodHandles.insertArguments(ctrMH, 0, args);
|
||||
// change return type to Object
|
||||
// (T1, T2, ..., Tn):TR -> (T1, T2, ..., Tn):Object
|
||||
mh = mh.asType(mh.type().changeReturnType(Object.class));
|
||||
|
||||
// drop last 2 arguments representing primValues and objValues arrays
|
||||
// (T1, T2, ..., Tn):Object -> (T1, T2, ..., Tn, byte[], Object[]):Object
|
||||
mh = MethodHandles.dropArguments(mh, mh.type().parameterCount(), byte[].class, Object[].class);
|
||||
|
||||
for (int i = recordComponents.length-1; i >= 0; i--) {
|
||||
String name = recordComponents[i].getName();
|
||||
Class<?> type = recordComponents[i].getType();
|
||||
// obtain stream field extractor that extracts argument at
|
||||
// position i (Ti+1) from primValues and objValues arrays
|
||||
// (byte[], Object[]):Ti+1
|
||||
MethodHandle combiner = streamFieldExtractor(name, type, desc);
|
||||
// fold byte[] privValues and Object[] objValues into argument at position i (Ti+1)
|
||||
// (..., Ti, Ti+1, byte[], Object[]):Object -> (..., Ti, byte[], Object[]):Object
|
||||
mh = MethodHandles.foldArguments(mh, i, combiner);
|
||||
}
|
||||
// what we are left with is a MethodHandle taking just the primValues
|
||||
// and objValues arrays and returning the constructed record instance
|
||||
// (byte[], Object[]):Object
|
||||
|
||||
// store it into cache and return the 1st value stored
|
||||
return desc.deserializationCtr =
|
||||
desc.deserializationCtrs.putIfAbsentAndGet(desc.getFields(false), mh);
|
||||
}
|
||||
|
||||
/** Returns the number of primitive fields for the given descriptor. */
|
||||
@ -2569,37 +2723,15 @@ public class ObjectStreamClass implements Serializable {
|
||||
return primValueCount;
|
||||
}
|
||||
|
||||
/** Returns the default value for the given type. */
|
||||
private static Object defaultValueFor(Class<?> pType) {
|
||||
if (pType == Integer.TYPE)
|
||||
return 0;
|
||||
else if (pType == Byte.TYPE)
|
||||
return (byte)0;
|
||||
else if (pType == Long.TYPE)
|
||||
return 0L;
|
||||
else if (pType == Float.TYPE)
|
||||
return 0.0f;
|
||||
else if (pType == Double.TYPE)
|
||||
return 0.0d;
|
||||
else if (pType == Short.TYPE)
|
||||
return (short)0;
|
||||
else if (pType == Character.TYPE)
|
||||
return '\u0000';
|
||||
else if (pType == Boolean.TYPE)
|
||||
return false;
|
||||
else
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the stream field value for the given name. The default value
|
||||
* for the given type is returned if the field value is absent.
|
||||
* Returns extractor MethodHandle taking the primValues and objValues arrays
|
||||
* and extracting the argument of canonical constructor with given name and type
|
||||
* or producing default value for the given type if the field is absent.
|
||||
*/
|
||||
private static Object streamFieldValue(String pName,
|
||||
Class<?> pType,
|
||||
ObjectStreamClass desc,
|
||||
ObjectInputStream.FieldValues fieldValues) {
|
||||
ObjectStreamField[] fields = desc.getFields();
|
||||
private static MethodHandle streamFieldExtractor(String pName,
|
||||
Class<?> pType,
|
||||
ObjectStreamClass desc) {
|
||||
ObjectStreamField[] fields = desc.getFields(false);
|
||||
|
||||
for (int i = 0; i < fields.length; i++) {
|
||||
ObjectStreamField f = fields[i];
|
||||
@ -2612,30 +2744,62 @@ public class ObjectStreamClass implements Serializable {
|
||||
throw new InternalError(fName + " unassignable, pType:" + pType + ", fType:" + fType);
|
||||
|
||||
if (f.isPrimitive()) {
|
||||
if (pType == Integer.TYPE)
|
||||
return Bits.getInt(fieldValues.primValues, f.getOffset());
|
||||
else if (fType == Byte.TYPE)
|
||||
return fieldValues.primValues[f.getOffset()];
|
||||
else if (fType == Long.TYPE)
|
||||
return Bits.getLong(fieldValues.primValues, f.getOffset());
|
||||
else if (fType == Float.TYPE)
|
||||
return Bits.getFloat(fieldValues.primValues, f.getOffset());
|
||||
else if (fType == Double.TYPE)
|
||||
return Bits.getDouble(fieldValues.primValues, f.getOffset());
|
||||
else if (fType == Short.TYPE)
|
||||
return Bits.getShort(fieldValues.primValues, f.getOffset());
|
||||
else if (fType == Character.TYPE)
|
||||
return Bits.getChar(fieldValues.primValues, f.getOffset());
|
||||
else if (fType == Boolean.TYPE)
|
||||
return Bits.getBoolean(fieldValues.primValues, f.getOffset());
|
||||
else
|
||||
// (byte[], int):fType
|
||||
MethodHandle mh = PRIM_VALUE_EXTRACTORS.get(fType);
|
||||
if (mh == null) {
|
||||
throw new InternalError("Unexpected type: " + fType);
|
||||
}
|
||||
// bind offset
|
||||
// (byte[], int):fType -> (byte[]):fType
|
||||
mh = MethodHandles.insertArguments(mh, 1, f.getOffset());
|
||||
// drop objValues argument
|
||||
// (byte[]):fType -> (byte[], Object[]):fType
|
||||
mh = MethodHandles.dropArguments(mh, 1, Object[].class);
|
||||
// adapt return type to pType
|
||||
// (byte[], Object[]):fType -> (byte[], Object[]):pType
|
||||
if (pType != fType) {
|
||||
mh = mh.asType(mh.type().changeReturnType(pType));
|
||||
}
|
||||
return mh;
|
||||
} else { // reference
|
||||
return fieldValues.objValues[i - numberPrimValues(desc)];
|
||||
// (Object[], int):Object
|
||||
MethodHandle mh = MethodHandles.arrayElementGetter(Object[].class);
|
||||
// bind index
|
||||
// (Object[], int):Object -> (Object[]):Object
|
||||
mh = MethodHandles.insertArguments(mh, 1, i - numberPrimValues(desc));
|
||||
// drop primValues argument
|
||||
// (Object[]):Object -> (byte[], Object[]):Object
|
||||
mh = MethodHandles.dropArguments(mh, 0, byte[].class);
|
||||
// adapt return type to pType
|
||||
// (byte[], Object[]):Object -> (byte[], Object[]):pType
|
||||
if (pType != Object.class) {
|
||||
mh = mh.asType(mh.type().changeReturnType(pType));
|
||||
}
|
||||
return mh;
|
||||
}
|
||||
}
|
||||
|
||||
return defaultValueFor(pType);
|
||||
// return default value extractor if no field matches pName
|
||||
return MethodHandles.empty(MethodType.methodType(pType, byte[].class, Object[].class));
|
||||
}
|
||||
|
||||
private static final Map<Class<?>, MethodHandle> PRIM_VALUE_EXTRACTORS;
|
||||
static {
|
||||
var lkp = MethodHandles.lookup();
|
||||
try {
|
||||
PRIM_VALUE_EXTRACTORS = Map.of(
|
||||
byte.class, MethodHandles.arrayElementGetter(byte[].class),
|
||||
short.class, lkp.findStatic(Bits.class, "getShort", MethodType.methodType(short.class, byte[].class, int.class)),
|
||||
int.class, lkp.findStatic(Bits.class, "getInt", MethodType.methodType(int.class, byte[].class, int.class)),
|
||||
long.class, lkp.findStatic(Bits.class, "getLong", MethodType.methodType(long.class, byte[].class, int.class)),
|
||||
float.class, lkp.findStatic(Bits.class, "getFloat", MethodType.methodType(float.class, byte[].class, int.class)),
|
||||
double.class, lkp.findStatic(Bits.class, "getDouble", MethodType.methodType(double.class, byte[].class, int.class)),
|
||||
char.class, lkp.findStatic(Bits.class, "getChar", MethodType.methodType(char.class, byte[].class, int.class)),
|
||||
boolean.class, lkp.findStatic(Bits.class, "getBoolean", MethodType.methodType(boolean.class, byte[].class, int.class))
|
||||
);
|
||||
} catch (NoSuchMethodException | IllegalAccessException e) {
|
||||
throw new InternalError("Can't lookup Bits.getXXX", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,563 @@
|
||||
/*
|
||||
* Copyright (c) 2020, Oracle and/or its affiliates. All rights reserved.
|
||||
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||
*
|
||||
* This code is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License version 2 only, as
|
||||
* published by the Free Software Foundation.
|
||||
*
|
||||
* This code is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
|
||||
* version 2 for more details (a copy is included in the LICENSE file that
|
||||
* accompanied this code).
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License version
|
||||
* 2 along with this work; if not, write to the Free Software Foundation,
|
||||
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
*
|
||||
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
|
||||
* or visit www.oracle.com if you need additional information or have any
|
||||
* questions.
|
||||
*/
|
||||
|
||||
/*
|
||||
* @test
|
||||
* @summary Checks that the appropriate value is given to the canonical ctr
|
||||
* @compile --enable-preview -source ${jdk.version} DifferentStreamFieldsTest.java
|
||||
* @run testng/othervm --enable-preview DifferentStreamFieldsTest
|
||||
* @run testng/othervm/java.security.policy=empty_security.policy --enable-preview DifferentStreamFieldsTest
|
||||
*/
|
||||
|
||||
import org.testng.annotations.DataProvider;
|
||||
import org.testng.annotations.Test;
|
||||
|
||||
import static java.io.ObjectStreamConstants.SC_SERIALIZABLE;
|
||||
import static java.io.ObjectStreamConstants.STREAM_MAGIC;
|
||||
import static java.io.ObjectStreamConstants.STREAM_VERSION;
|
||||
import static java.io.ObjectStreamConstants.TC_CLASSDESC;
|
||||
import static java.io.ObjectStreamConstants.TC_ENDBLOCKDATA;
|
||||
import static java.io.ObjectStreamConstants.TC_NULL;
|
||||
import static java.io.ObjectStreamConstants.TC_OBJECT;
|
||||
import static java.io.ObjectStreamConstants.TC_STRING;
|
||||
import static java.lang.System.out;
|
||||
import static org.testng.Assert.assertEquals;
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.DataOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InvalidClassException;
|
||||
import java.io.InvalidObjectException;
|
||||
import java.io.ObjectInputStream;
|
||||
import java.io.ObjectOutputStream;
|
||||
import java.io.Serializable;
|
||||
import java.io.UncheckedIOException;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* Checks that the appropriate value is given to the canonical ctr.
|
||||
*/
|
||||
public class DifferentStreamFieldsTest {
|
||||
|
||||
record R01(boolean x) implements Serializable {}
|
||||
|
||||
record R02(byte x) implements Serializable {}
|
||||
|
||||
record R03(short x) implements Serializable {}
|
||||
|
||||
record R04(char x) implements Serializable {}
|
||||
|
||||
record R05(int x) implements Serializable {}
|
||||
|
||||
record R06(long x) implements Serializable {}
|
||||
|
||||
record R07(float x) implements Serializable {}
|
||||
|
||||
record R08(double x) implements Serializable {}
|
||||
|
||||
record R09(Object x) implements Serializable {}
|
||||
|
||||
record R10(String x) implements Serializable {}
|
||||
|
||||
record R11(int[]x) implements Serializable {}
|
||||
|
||||
record R12(Object[]x) implements Serializable {}
|
||||
|
||||
record R13(R12 x) implements Serializable {}
|
||||
|
||||
record R14(R13[]x) implements Serializable {}
|
||||
|
||||
@DataProvider(name = "recordTypeAndExpectedValue")
|
||||
public Object[][] recordTypeAndExpectedValue() {
|
||||
return new Object[][]{
|
||||
new Object[]{R01.class, false},
|
||||
new Object[]{R02.class, (byte) 0},
|
||||
new Object[]{R03.class, (short) 0},
|
||||
new Object[]{R04.class, '\u0000'},
|
||||
new Object[]{R05.class, 0},
|
||||
new Object[]{R06.class, 0L},
|
||||
new Object[]{R07.class, 0.0f},
|
||||
new Object[]{R08.class, 0.0d},
|
||||
new Object[]{R09.class, null},
|
||||
new Object[]{R10.class, null},
|
||||
new Object[]{R11.class, null},
|
||||
new Object[]{R12.class, null},
|
||||
new Object[]{R13.class, null},
|
||||
new Object[]{R14.class, null}
|
||||
};
|
||||
}
|
||||
|
||||
@Test(dataProvider = "recordTypeAndExpectedValue")
|
||||
public void testWithDifferentTypes(Class<?> clazz, Object expectedXValue)
|
||||
throws Exception {
|
||||
out.println("\n---");
|
||||
assert clazz.isRecord();
|
||||
byte[] bytes = SerialByteStreamBuilder
|
||||
.newBuilder(clazz.getName())
|
||||
.build();
|
||||
|
||||
Object obj = deserialize(bytes);
|
||||
out.println("deserialized: " + obj);
|
||||
Object actualXValue = clazz.getDeclaredMethod("x").invoke(obj);
|
||||
assertEquals(actualXValue, expectedXValue);
|
||||
|
||||
bytes = SerialByteStreamBuilder
|
||||
.newBuilder(clazz.getName())
|
||||
.addPrimitiveField("y", int.class, 5) // stream junk
|
||||
.build();
|
||||
|
||||
obj = deserialize(bytes);
|
||||
out.println("deserialized: " + obj);
|
||||
actualXValue = clazz.getDeclaredMethod("x").invoke(obj);
|
||||
assertEquals(actualXValue, expectedXValue);
|
||||
}
|
||||
|
||||
// --- all together
|
||||
|
||||
@Test
|
||||
public void testWithAllTogether() throws Exception {
|
||||
out.println("\n---");
|
||||
record R15(boolean a, byte b, short c, char d, int e, long f, float g,
|
||||
double h, Object i, String j, long[]k, Object[]l)
|
||||
implements Serializable {}
|
||||
|
||||
byte[] bytes = SerialByteStreamBuilder
|
||||
.newBuilder(R15.class.getName())
|
||||
.addPrimitiveField("x", int.class, 5) // stream junk
|
||||
.build();
|
||||
|
||||
R15 obj = deserialize(bytes);
|
||||
out.println("deserialized: " + obj);
|
||||
assertEquals(obj.a, false);
|
||||
assertEquals(obj.b, 0);
|
||||
assertEquals(obj.c, 0);
|
||||
assertEquals(obj.d, '\u0000');
|
||||
assertEquals(obj.e, 0);
|
||||
assertEquals(obj.f, 0l);
|
||||
assertEquals(obj.g, 0f);
|
||||
assertEquals(obj.h, 0d);
|
||||
assertEquals(obj.i, null);
|
||||
assertEquals(obj.j, null);
|
||||
assertEquals(obj.k, null);
|
||||
assertEquals(obj.l, null);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testInt() throws Exception {
|
||||
out.println("\n---");
|
||||
{
|
||||
record R(int x) implements Serializable {}
|
||||
|
||||
var r = new R(5);
|
||||
byte[] OOSBytes = serialize(r);
|
||||
|
||||
byte[] builderBytes = SerialByteStreamBuilder
|
||||
.newBuilder(R.class.getName())
|
||||
.addPrimitiveField("x", int.class, 5)
|
||||
.build();
|
||||
|
||||
var deser1 = deserialize(OOSBytes);
|
||||
assertEquals(deser1, r);
|
||||
var deser2 = deserialize(builderBytes);
|
||||
assertEquals(deser2, deser1);
|
||||
}
|
||||
{
|
||||
record R(int x, int y) implements Serializable {}
|
||||
|
||||
var r = new R(7, 8);
|
||||
byte[] OOSBytes = serialize(r);
|
||||
var deser1 = deserialize(OOSBytes);
|
||||
assertEquals(deser1, r);
|
||||
|
||||
byte[] builderBytes = SerialByteStreamBuilder
|
||||
.newBuilder(R.class.getName())
|
||||
.addPrimitiveField("x", int.class, 7)
|
||||
.addPrimitiveField("y", int.class, 8)
|
||||
.build();
|
||||
|
||||
var deser2 = deserialize(builderBytes);
|
||||
assertEquals(deser2, deser1);
|
||||
|
||||
builderBytes = SerialByteStreamBuilder
|
||||
.newBuilder(R.class.getName())
|
||||
.addPrimitiveField("y", int.class, 8) // reverse order
|
||||
.addPrimitiveField("x", int.class, 7)
|
||||
.build();
|
||||
deser2 = deserialize(builderBytes);
|
||||
assertEquals(deser2, deser1);
|
||||
|
||||
builderBytes = SerialByteStreamBuilder
|
||||
.newBuilder(R.class.getName())
|
||||
.addPrimitiveField("w", int.class, 6) // additional fields
|
||||
.addPrimitiveField("x", int.class, 7)
|
||||
.addPrimitiveField("y", int.class, 8)
|
||||
.addPrimitiveField("z", int.class, 9) // additional fields
|
||||
.build();
|
||||
deser2 = deserialize(builderBytes);
|
||||
assertEquals(deser2, deser1);
|
||||
|
||||
r = new R(0, 0);
|
||||
OOSBytes = serialize(r);
|
||||
deser1 = deserialize(OOSBytes);
|
||||
assertEquals(deser1, r);
|
||||
|
||||
builderBytes = SerialByteStreamBuilder
|
||||
.newBuilder(R.class.getName())
|
||||
.addPrimitiveField("y", int.class, 0)
|
||||
.addPrimitiveField("x", int.class, 0)
|
||||
.build();
|
||||
deser2 = deserialize(builderBytes);
|
||||
assertEquals(deser2, deser1);
|
||||
|
||||
builderBytes = SerialByteStreamBuilder
|
||||
.newBuilder(R.class.getName()) // no field values
|
||||
.build();
|
||||
deser2 = deserialize(builderBytes);
|
||||
assertEquals(deser2, deser1);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testString() throws Exception {
|
||||
out.println("\n---");
|
||||
|
||||
record Str(String part1, String part2) implements Serializable {}
|
||||
|
||||
var r = new Str("Hello", "World!");
|
||||
var deser1 = deserialize(serialize(r));
|
||||
assertEquals(deser1, r);
|
||||
|
||||
byte[] builderBytes = SerialByteStreamBuilder
|
||||
.newBuilder(Str.class.getName())
|
||||
.addField("part1", String.class, "Hello")
|
||||
.addField("part2", String.class, "World!")
|
||||
.build();
|
||||
|
||||
var deser2 = deserialize(builderBytes);
|
||||
assertEquals(deser2, deser1);
|
||||
|
||||
builderBytes = SerialByteStreamBuilder
|
||||
.newBuilder(Str.class.getName())
|
||||
.addField("cruft", String.class, "gg")
|
||||
.addField("part1", String.class, "Hello")
|
||||
.addField("part2", String.class, "World!")
|
||||
.addPrimitiveField("x", int.class, 13)
|
||||
.build();
|
||||
|
||||
var deser3 = deserialize(builderBytes);
|
||||
assertEquals(deser3, deser1);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testArrays() throws Exception {
|
||||
out.println("\n---");
|
||||
{
|
||||
record IntArray(int[]ints, long[]longs) implements Serializable {}
|
||||
IntArray r = new IntArray(new int[]{5, 4, 3, 2, 1}, new long[]{9L});
|
||||
IntArray deser1 = deserialize(serialize(r));
|
||||
assertEquals(deser1.ints(), r.ints());
|
||||
assertEquals(deser1.longs(), r.longs());
|
||||
|
||||
byte[] builderBytes = SerialByteStreamBuilder
|
||||
.newBuilder(IntArray.class.getName())
|
||||
.addField("ints", int[].class, new int[]{5, 4, 3, 2, 1})
|
||||
.addField("longs", long[].class, new long[]{9L})
|
||||
.build();
|
||||
|
||||
IntArray deser2 = deserialize(builderBytes);
|
||||
assertEquals(deser2.ints(), deser1.ints());
|
||||
assertEquals(deser2.longs(), deser1.longs());
|
||||
}
|
||||
{
|
||||
record StrArray(String[]stringArray) implements Serializable {}
|
||||
StrArray r = new StrArray(new String[]{"foo", "bar"});
|
||||
StrArray deser1 = deserialize(serialize(r));
|
||||
assertEquals(deser1.stringArray(), r.stringArray());
|
||||
|
||||
byte[] builderBytes = SerialByteStreamBuilder
|
||||
.newBuilder(StrArray.class.getName())
|
||||
.addField("stringArray", String[].class, new String[]{"foo", "bar"})
|
||||
.build();
|
||||
|
||||
StrArray deser2 = deserialize(builderBytes);
|
||||
assertEquals(deser2.stringArray(), deser1.stringArray());
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCompatibleFieldTypeChange() throws Exception {
|
||||
out.println("\n---");
|
||||
|
||||
{
|
||||
record NumberHolder(Number n) implements Serializable {}
|
||||
|
||||
var r = new NumberHolder(123);
|
||||
var deser1 = deserialize(serialize(r));
|
||||
assertEquals(deser1, r);
|
||||
|
||||
byte[] builderBytes = SerialByteStreamBuilder
|
||||
.newBuilder(NumberHolder.class.getName())
|
||||
.addField("n", Integer.class, 123)
|
||||
.build();
|
||||
|
||||
var deser2 = deserialize(builderBytes);
|
||||
assertEquals(deser2, deser1);
|
||||
}
|
||||
|
||||
{
|
||||
record IntegerHolder(Integer i) implements Serializable {}
|
||||
|
||||
var r = new IntegerHolder(123);
|
||||
var deser1 = deserialize(serialize(r));
|
||||
assertEquals(deser1, r);
|
||||
|
||||
byte[] builderBytes = SerialByteStreamBuilder
|
||||
.newBuilder(IntegerHolder.class.getName())
|
||||
.addField("i", Number.class, 123)
|
||||
.build();
|
||||
|
||||
var deser2 = deserialize(builderBytes);
|
||||
assertEquals(deser2, deser1);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testIncompatibleRefFieldTypeChange() throws Exception {
|
||||
out.println("\n---");
|
||||
|
||||
record StringHolder(String s) implements Serializable {}
|
||||
|
||||
var r = new StringHolder("123");
|
||||
var deser1 = deserialize(serialize(r));
|
||||
assertEquals(deser1, r);
|
||||
|
||||
byte[] builderBytes = SerialByteStreamBuilder
|
||||
.newBuilder(StringHolder.class.getName())
|
||||
.addField("s", Integer.class, 123)
|
||||
.build();
|
||||
|
||||
try {
|
||||
var deser2 = deserialize(builderBytes);
|
||||
throw new AssertionError(
|
||||
"Unexpected success of deserialization. Deserialized value: " + deser2);
|
||||
} catch (InvalidObjectException e) {
|
||||
// expected
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testIncompatiblePrimitiveFieldTypeChange() throws Exception {
|
||||
out.println("\n---");
|
||||
|
||||
record IntHolder(int i) implements Serializable {}
|
||||
|
||||
var r = new IntHolder(123);
|
||||
var deser1 = deserialize(serialize(r));
|
||||
assertEquals(deser1, r);
|
||||
|
||||
byte[] builderBytes = SerialByteStreamBuilder
|
||||
.newBuilder(IntHolder.class.getName())
|
||||
.addPrimitiveField("i", long.class, 123L)
|
||||
.build();
|
||||
|
||||
try {
|
||||
var deser2 = deserialize(builderBytes);
|
||||
throw new AssertionError(
|
||||
"Unexpected success of deserialization. Deserialized value: " + deser2);
|
||||
} catch (InvalidClassException e) {
|
||||
// expected
|
||||
}
|
||||
}
|
||||
|
||||
<T> byte[] serialize(T obj) throws IOException {
|
||||
ByteArrayOutputStream baos = new ByteArrayOutputStream();
|
||||
ObjectOutputStream oos = new ObjectOutputStream(baos);
|
||||
oos.writeObject(obj);
|
||||
oos.close();
|
||||
return baos.toByteArray();
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
static <T> T deserialize(byte[] streamBytes)
|
||||
throws IOException, ClassNotFoundException {
|
||||
ByteArrayInputStream bais = new ByteArrayInputStream(streamBytes);
|
||||
ObjectInputStream ois = new ObjectInputStream(bais);
|
||||
return (T) ois.readObject();
|
||||
}
|
||||
|
||||
static class SerialByteStreamBuilder {
|
||||
|
||||
private final ObjectOutputStream objectOutputStream;
|
||||
private final ByteArrayOutputStream byteArrayOutputStream;
|
||||
|
||||
record NameAndType<T>(String name, Class<T>type) {}
|
||||
|
||||
private String className;
|
||||
private final LinkedHashMap<NameAndType<?>, Object> primFields = new LinkedHashMap<>();
|
||||
private final LinkedHashMap<NameAndType<?>, Object> objectFields = new LinkedHashMap<>();
|
||||
|
||||
private SerialByteStreamBuilder() {
|
||||
try {
|
||||
byteArrayOutputStream = new ByteArrayOutputStream();
|
||||
objectOutputStream = new ObjectOutputStream(byteArrayOutputStream);
|
||||
} catch (IOException e) {
|
||||
throw new UncheckedIOException(e);
|
||||
}
|
||||
}
|
||||
|
||||
public static SerialByteStreamBuilder newBuilder(String className) {
|
||||
return (new SerialByteStreamBuilder()).className(className);
|
||||
}
|
||||
|
||||
private SerialByteStreamBuilder className(String className) {
|
||||
this.className = className;
|
||||
return this;
|
||||
}
|
||||
|
||||
public <T> SerialByteStreamBuilder addPrimitiveField(String name, Class<T> type, T value) {
|
||||
if (!type.isPrimitive())
|
||||
throw new IllegalArgumentException("Unexpected non-primitive field: " + type);
|
||||
primFields.put(new NameAndType<>(name, type), value);
|
||||
return this;
|
||||
}
|
||||
|
||||
public <T> SerialByteStreamBuilder addField(String name, Class<T> type, T value) {
|
||||
if (type.isPrimitive())
|
||||
throw new IllegalArgumentException("Unexpected primitive field: " + type);
|
||||
objectFields.put(new NameAndType<>(name, type), value);
|
||||
return this;
|
||||
}
|
||||
|
||||
private static int getPrimitiveSignature(Class<?> cl) {
|
||||
if (cl == Integer.TYPE) return 'I';
|
||||
else if (cl == Byte.TYPE) return 'B';
|
||||
else if (cl == Long.TYPE) return 'J';
|
||||
else if (cl == Float.TYPE) return 'F';
|
||||
else if (cl == Double.TYPE) return 'D';
|
||||
else if (cl == Short.TYPE) return 'S';
|
||||
else if (cl == Character.TYPE) return 'C';
|
||||
else if (cl == Boolean.TYPE) return 'Z';
|
||||
else throw new InternalError();
|
||||
}
|
||||
|
||||
private static void writeUTF(DataOutputStream out, String str) throws IOException {
|
||||
int utflen = str.length(); // assume ASCII
|
||||
assert utflen <= 0xFFFF;
|
||||
out.writeShort(utflen);
|
||||
out.writeBytes(str);
|
||||
}
|
||||
|
||||
private void writePrimFieldsDesc(DataOutputStream out) throws IOException {
|
||||
for (Map.Entry<NameAndType<?>, Object> entry : primFields.entrySet()) {
|
||||
assert entry.getKey().type() != void.class;
|
||||
out.writeByte(getPrimitiveSignature(entry.getKey().type())); // prim_typecode
|
||||
out.writeUTF(entry.getKey().name()); // fieldName
|
||||
}
|
||||
}
|
||||
|
||||
private void writePrimFieldsValues(DataOutputStream out) throws IOException {
|
||||
for (Map.Entry<NameAndType<?>, Object> entry : primFields.entrySet()) {
|
||||
Class<?> cl = entry.getKey().type();
|
||||
Object value = entry.getValue();
|
||||
if (cl == Integer.TYPE) out.writeInt((int) value);
|
||||
else if (cl == Byte.TYPE) out.writeByte((byte) value);
|
||||
else if (cl == Long.TYPE) out.writeLong((long) value);
|
||||
else if (cl == Float.TYPE) out.writeFloat((float) value);
|
||||
else if (cl == Double.TYPE) out.writeDouble((double) value);
|
||||
else if (cl == Short.TYPE) out.writeShort((short) value);
|
||||
else if (cl == Character.TYPE) out.writeChar((char) value);
|
||||
else if (cl == Boolean.TYPE) out.writeBoolean((boolean) value);
|
||||
else throw new InternalError();
|
||||
}
|
||||
}
|
||||
|
||||
private void writeObjectFieldDesc(DataOutputStream out) throws IOException {
|
||||
for (Map.Entry<NameAndType<?>, Object> entry : objectFields.entrySet()) {
|
||||
Class<?> cl = entry.getKey().type();
|
||||
assert !cl.isPrimitive();
|
||||
// obj_typecode
|
||||
if (cl.isArray()) {
|
||||
out.writeByte('[');
|
||||
} else {
|
||||
out.writeByte('L');
|
||||
}
|
||||
writeUTF(out, entry.getKey().name());
|
||||
out.writeByte(TC_STRING);
|
||||
writeUTF(out,
|
||||
(cl.isArray() ? cl.getName() : "L" + cl.getName() + ";")
|
||||
.replace('.', '/'));
|
||||
}
|
||||
}
|
||||
|
||||
private void writeObject(DataOutputStream out, Object value) throws IOException {
|
||||
objectOutputStream.reset();
|
||||
byteArrayOutputStream.reset();
|
||||
objectOutputStream.writeUnshared(value);
|
||||
out.write(byteArrayOutputStream.toByteArray());
|
||||
}
|
||||
|
||||
private void writeObjectFieldValues(DataOutputStream out) throws IOException {
|
||||
for (Map.Entry<NameAndType<?>, Object> entry : objectFields.entrySet()) {
|
||||
Class<?> cl = entry.getKey().type();
|
||||
assert !cl.isPrimitive();
|
||||
if (cl == String.class) {
|
||||
out.writeByte(TC_STRING);
|
||||
writeUTF(out, (String) entry.getValue());
|
||||
} else {
|
||||
writeObject(out, entry.getValue());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private int numFields() {
|
||||
return primFields.size() + objectFields.size();
|
||||
}
|
||||
|
||||
public byte[] build() {
|
||||
try {
|
||||
ByteArrayOutputStream baos = new ByteArrayOutputStream();
|
||||
DataOutputStream dos = new DataOutputStream(baos);
|
||||
dos.writeShort(STREAM_MAGIC);
|
||||
dos.writeShort(STREAM_VERSION);
|
||||
dos.writeByte(TC_OBJECT);
|
||||
dos.writeByte(TC_CLASSDESC);
|
||||
dos.writeUTF(className);
|
||||
dos.writeLong(0L);
|
||||
dos.writeByte(SC_SERIALIZABLE);
|
||||
dos.writeShort(numFields()); // number of fields
|
||||
writePrimFieldsDesc(dos);
|
||||
writeObjectFieldDesc(dos);
|
||||
dos.writeByte(TC_ENDBLOCKDATA); // no annotations
|
||||
dos.writeByte(TC_NULL); // no superclasses
|
||||
writePrimFieldsValues(dos);
|
||||
writeObjectFieldValues(dos);
|
||||
dos.close();
|
||||
return baos.toByteArray();
|
||||
} catch (IOException unexpected) {
|
||||
throw new AssertionError(unexpected);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
174
test/micro/org/openjdk/bench/java/io/RecordDeserialization.java
Normal file
174
test/micro/org/openjdk/bench/java/io/RecordDeserialization.java
Normal file
@ -0,0 +1,174 @@
|
||||
/*
|
||||
* Copyright (c) 2020, Oracle and/or its affiliates. All rights reserved.
|
||||
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||
*
|
||||
* This code is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License version 2 only, as
|
||||
* published by the Free Software Foundation.
|
||||
*
|
||||
* This code is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
|
||||
* version 2 for more details (a copy is included in the LICENSE file that
|
||||
* accompanied this code).
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License version
|
||||
* 2 along with this work; if not, write to the Free Software Foundation,
|
||||
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
*
|
||||
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
|
||||
* or visit www.oracle.com if you need additional information or have any
|
||||
* questions.
|
||||
*/
|
||||
|
||||
/*
|
||||
|
||||
Sample runs on Ryzen 3700X:
|
||||
|
||||
before 8247532:
|
||||
|
||||
Benchmark (length) Mode Cnt Score Error Units
|
||||
RecordDeserialization.deserializeClasses 10 avgt 10 8.382 : 0.013 us/op
|
||||
RecordDeserialization.deserializeClasses 100 avgt 10 33.736 : 0.171 us/op
|
||||
RecordDeserialization.deserializeClasses 1000 avgt 10 271.224 : 0.953 us/op
|
||||
RecordDeserialization.deserializeRecords 10 avgt 10 58.606 : 0.446 us/op
|
||||
RecordDeserialization.deserializeRecords 100 avgt 10 530.044 : 1.752 us/op
|
||||
RecordDeserialization.deserializeRecords 1000 avgt 10 5335.624 : 44.942 us/op
|
||||
|
||||
after 8247532:
|
||||
|
||||
Benchmark (length) Mode Cnt Score Error Units
|
||||
RecordDeserialization.deserializeClasses 10 avgt 10 8.681 : 0.155 us/op
|
||||
RecordDeserialization.deserializeClasses 100 avgt 10 32.496 : 0.087 us/op
|
||||
RecordDeserialization.deserializeClasses 1000 avgt 10 279.014 : 1.189 us/op
|
||||
RecordDeserialization.deserializeRecords 10 avgt 10 8.537 : 0.032 us/op
|
||||
RecordDeserialization.deserializeRecords 100 avgt 10 31.451 : 0.083 us/op
|
||||
RecordDeserialization.deserializeRecords 1000 avgt 10 250.854 : 2.772 us/op
|
||||
|
||||
*/
|
||||
|
||||
package org.openjdk.bench.java.io;
|
||||
|
||||
import org.openjdk.jmh.annotations.Benchmark;
|
||||
import org.openjdk.jmh.annotations.BenchmarkMode;
|
||||
import org.openjdk.jmh.annotations.Fork;
|
||||
import org.openjdk.jmh.annotations.Level;
|
||||
import org.openjdk.jmh.annotations.Measurement;
|
||||
import org.openjdk.jmh.annotations.Mode;
|
||||
import org.openjdk.jmh.annotations.OutputTimeUnit;
|
||||
import org.openjdk.jmh.annotations.Param;
|
||||
import org.openjdk.jmh.annotations.Scope;
|
||||
import org.openjdk.jmh.annotations.Setup;
|
||||
import org.openjdk.jmh.annotations.State;
|
||||
import org.openjdk.jmh.annotations.Warmup;
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.ObjectInputStream;
|
||||
import java.io.ObjectOutputStream;
|
||||
import java.io.Serializable;
|
||||
import java.io.UncheckedIOException;
|
||||
import java.util.concurrent.ThreadLocalRandom;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.stream.IntStream;
|
||||
|
||||
/**
|
||||
* A micro benchmark used to measure/compare the performance of
|
||||
* de-serializing record(s) vs. classical class(es)
|
||||
*/
|
||||
@BenchmarkMode(Mode.AverageTime)
|
||||
@Warmup(iterations = 5, time = 1)
|
||||
@Measurement(iterations = 10, time = 1)
|
||||
@OutputTimeUnit(TimeUnit.MICROSECONDS)
|
||||
@State(Scope.Thread)
|
||||
@Fork(value = 1, warmups = 0, jvmArgsAppend = "--enable-preview")
|
||||
public class RecordDeserialization {
|
||||
|
||||
public record PointR(int x, int y) implements Serializable {}
|
||||
|
||||
public record LineR(PointR p1, PointR p2) implements Serializable {}
|
||||
|
||||
public static class PointC implements Serializable {
|
||||
private final int x, y;
|
||||
|
||||
public PointC(int x, int y) {
|
||||
this.x = x;
|
||||
this.y = y;
|
||||
}
|
||||
}
|
||||
|
||||
public static class LineC implements Serializable {
|
||||
private final PointC p1, p2;
|
||||
|
||||
public LineC(PointC p1, PointC p2) {
|
||||
this.p1 = p1;
|
||||
this.p2 = p2;
|
||||
}
|
||||
}
|
||||
|
||||
private byte[] lineRsBytes, lineCsBytes;
|
||||
|
||||
private static LineR newLineR() {
|
||||
ThreadLocalRandom rnd = ThreadLocalRandom.current();
|
||||
return new LineR(new PointR(rnd.nextInt(), rnd.nextInt()),
|
||||
new PointR(rnd.nextInt(), rnd.nextInt()));
|
||||
}
|
||||
|
||||
private static LineC newLineC() {
|
||||
ThreadLocalRandom rnd = ThreadLocalRandom.current();
|
||||
return new LineC(new PointC(rnd.nextInt(), rnd.nextInt()),
|
||||
new PointC(rnd.nextInt(), rnd.nextInt()));
|
||||
}
|
||||
|
||||
private static byte[] serialize(Object o) {
|
||||
try {
|
||||
ByteArrayOutputStream baos = new ByteArrayOutputStream();
|
||||
ObjectOutputStream oos = new ObjectOutputStream(baos);
|
||||
oos.writeObject(o);
|
||||
oos.close();
|
||||
return baos.toByteArray();
|
||||
} catch (IOException e) {
|
||||
throw new UncheckedIOException(e);
|
||||
}
|
||||
}
|
||||
|
||||
private static Object deserialize(byte[] bytes) {
|
||||
try {
|
||||
return new ObjectInputStream(new ByteArrayInputStream(bytes))
|
||||
.readObject();
|
||||
} catch (IOException e) {
|
||||
throw new UncheckedIOException(e);
|
||||
} catch (ClassNotFoundException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
@Param({"10", "100", "1000"})
|
||||
public int length;
|
||||
|
||||
@Setup(Level.Trial)
|
||||
public void setup() {
|
||||
LineR[] lineRs = IntStream
|
||||
.range(0, length)
|
||||
.mapToObj(i -> newLineR())
|
||||
.toArray(LineR[]::new);
|
||||
lineRsBytes = serialize(lineRs);
|
||||
|
||||
LineC[] lineCs = IntStream
|
||||
.range(0, length)
|
||||
.mapToObj(i -> newLineC())
|
||||
.toArray(LineC[]::new);
|
||||
lineCsBytes = serialize(lineCs);
|
||||
}
|
||||
|
||||
@Benchmark
|
||||
public Object deserializeRecords() {
|
||||
return deserialize(lineRsBytes);
|
||||
}
|
||||
|
||||
@Benchmark
|
||||
public Object deserializeClasses() {
|
||||
return deserialize(lineCsBytes);
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user