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), \
|
TARGET_RELEASE := $(TARGET_RELEASE_NEWJDK_UPGRADED), \
|
||||||
SMALL_JAVA := false, \
|
SMALL_JAVA := false, \
|
||||||
CLASSPATH := $(MICROBENCHMARK_CLASSPATH), \
|
CLASSPATH := $(MICROBENCHMARK_CLASSPATH), \
|
||||||
DISABLED_WARNINGS := processing rawtypes cast serial, \
|
DISABLED_WARNINGS := processing rawtypes cast serial preview, \
|
||||||
SRC := $(MICROBENCHMARK_SRC), \
|
SRC := $(MICROBENCHMARK_SRC), \
|
||||||
BIN := $(MICROBENCHMARK_CLASSES), \
|
BIN := $(MICROBENCHMARK_CLASSES), \
|
||||||
JAVA_FLAGS := --add-modules jdk.unsupported --limit-modules java.management, \
|
JAVA_FLAGS := --add-modules jdk.unsupported --limit-modules java.management, \
|
||||||
|
JAVAC_FLAGS := --enable-preview, \
|
||||||
))
|
))
|
||||||
|
|
||||||
$(BUILD_JDK_MICROBENCHMARK): $(JMH_COMPILE_JARS)
|
$(BUILD_JDK_MICROBENCHMARK): $(JMH_COMPILE_JARS)
|
||||||
|
@ -2182,7 +2182,7 @@ public class ObjectInputStream
|
|||||||
handles.markException(passHandle, resolveEx);
|
handles.markException(passHandle, resolveEx);
|
||||||
}
|
}
|
||||||
|
|
||||||
final boolean isRecord = cl != null && isRecord(cl) ? true : false;
|
final boolean isRecord = cl != null && isRecord(cl);
|
||||||
if (isRecord) {
|
if (isRecord) {
|
||||||
assert obj == null;
|
assert obj == null;
|
||||||
obj = readRecord(desc);
|
obj = readRecord(desc);
|
||||||
@ -2289,14 +2289,14 @@ public class ObjectInputStream
|
|||||||
|
|
||||||
FieldValues fieldValues = defaultReadFields(null, desc);
|
FieldValues fieldValues = defaultReadFields(null, desc);
|
||||||
|
|
||||||
// retrieve the canonical constructor
|
// get canonical record constructor adapted to take two arguments:
|
||||||
MethodHandle ctrMH = desc.getRecordConstructor();
|
// - byte[] primValues
|
||||||
|
// - Object[] objValues
|
||||||
// bind the stream field values
|
// and return Object
|
||||||
ctrMH = RecordSupport.bindCtrValues(ctrMH, desc, fieldValues);
|
MethodHandle ctrMH = RecordSupport.deserializationCtr(desc);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
return ctrMH.invoke();
|
return (Object) ctrMH.invokeExact(fieldValues.primValues, fieldValues.objValues);
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
InvalidObjectException ioe = new InvalidObjectException(e.getMessage());
|
InvalidObjectException ioe = new InvalidObjectException(e.getMessage());
|
||||||
ioe.initCause(e);
|
ioe.initCause(e);
|
||||||
|
@ -27,6 +27,7 @@ package java.io;
|
|||||||
|
|
||||||
import java.lang.invoke.MethodHandle;
|
import java.lang.invoke.MethodHandle;
|
||||||
import java.lang.invoke.MethodHandles;
|
import java.lang.invoke.MethodHandles;
|
||||||
|
import java.lang.invoke.MethodType;
|
||||||
import java.lang.ref.Reference;
|
import java.lang.ref.Reference;
|
||||||
import java.lang.ref.ReferenceQueue;
|
import java.lang.ref.ReferenceQueue;
|
||||||
import java.lang.ref.SoftReference;
|
import java.lang.ref.SoftReference;
|
||||||
@ -55,6 +56,7 @@ import java.util.Arrays;
|
|||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.Comparator;
|
import java.util.Comparator;
|
||||||
import java.util.HashSet;
|
import java.util.HashSet;
|
||||||
|
import java.util.Map;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
import java.util.concurrent.ConcurrentHashMap;
|
import java.util.concurrent.ConcurrentHashMap;
|
||||||
import java.util.concurrent.ConcurrentMap;
|
import java.util.concurrent.ConcurrentMap;
|
||||||
@ -191,8 +193,14 @@ public class ObjectStreamClass implements Serializable {
|
|||||||
|
|
||||||
/** serialization-appropriate constructor, or null if none */
|
/** serialization-appropriate constructor, or null if none */
|
||||||
private Constructor<?> cons;
|
private Constructor<?> cons;
|
||||||
/** record canonical constructor, or null */
|
/** record canonical constructor (shared among OSCs for same class), or null */
|
||||||
private MethodHandle canonicalCtr;
|
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 */
|
/** protection domains that need to be checked when calling the constructor */
|
||||||
private ProtectionDomain[] domains;
|
private ProtectionDomain[] domains;
|
||||||
|
|
||||||
@ -525,6 +533,7 @@ public class ObjectStreamClass implements Serializable {
|
|||||||
|
|
||||||
if (isRecord) {
|
if (isRecord) {
|
||||||
canonicalCtr = canonicalRecordCtr(cl);
|
canonicalCtr = canonicalRecordCtr(cl);
|
||||||
|
deserializationCtrs = new DeserializationConstructorsCache();
|
||||||
} else if (externalizable) {
|
} else if (externalizable) {
|
||||||
cons = getExternalizableConstructor(cl);
|
cons = getExternalizableConstructor(cl);
|
||||||
} else {
|
} else {
|
||||||
@ -740,7 +749,10 @@ public class ObjectStreamClass implements Serializable {
|
|||||||
this.cl = cl;
|
this.cl = cl;
|
||||||
if (cl != null) {
|
if (cl != null) {
|
||||||
this.isRecord = isRecord(cl);
|
this.isRecord = isRecord(cl);
|
||||||
|
// canonical record constructor is shared
|
||||||
this.canonicalCtr = osc.canonicalCtr;
|
this.canonicalCtr = osc.canonicalCtr;
|
||||||
|
// cache of deserialization constructors is shared
|
||||||
|
this.deserializationCtrs = osc.deserializationCtrs;
|
||||||
}
|
}
|
||||||
this.resolveEx = resolveEx;
|
this.resolveEx = resolveEx;
|
||||||
this.superDesc = superDesc;
|
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. */
|
/** Record specific support for retrieving and binding stream field values. */
|
||||||
static final class RecordSupport {
|
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")
|
@SuppressWarnings("preview")
|
||||||
static MethodHandle bindCtrValues(MethodHandle ctrMH,
|
static MethodHandle deserializationCtr(ObjectStreamClass desc) {
|
||||||
ObjectStreamClass desc,
|
// check the cached value 1st
|
||||||
ObjectInputStream.FieldValues fieldValues) {
|
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;
|
RecordComponent[] recordComponents;
|
||||||
try {
|
try {
|
||||||
Class<?> cls = desc.forClass();
|
Class<?> cls = desc.forClass();
|
||||||
@ -2545,15 +2678,36 @@ public class ObjectStreamClass implements Serializable {
|
|||||||
throw new InternalError(e.getCause());
|
throw new InternalError(e.getCause());
|
||||||
}
|
}
|
||||||
|
|
||||||
Object[] args = new Object[recordComponents.length];
|
// retrieve the canonical constructor
|
||||||
for (int i = 0; i < recordComponents.length; i++) {
|
// (T1, T2, ..., Tn):TR
|
||||||
String name = recordComponents[i].getName();
|
mh = desc.getRecordConstructor();
|
||||||
Class<?> type= recordComponents[i].getType();
|
|
||||||
Object o = streamFieldValue(name, type, desc, fieldValues);
|
|
||||||
args[i] = o;
|
|
||||||
}
|
|
||||||
|
|
||||||
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. */
|
/** Returns the number of primitive fields for the given descriptor. */
|
||||||
@ -2569,37 +2723,15 @@ public class ObjectStreamClass implements Serializable {
|
|||||||
return primValueCount;
|
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
|
* Returns extractor MethodHandle taking the primValues and objValues arrays
|
||||||
* for the given type is returned if the field value is absent.
|
* 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,
|
private static MethodHandle streamFieldExtractor(String pName,
|
||||||
Class<?> pType,
|
Class<?> pType,
|
||||||
ObjectStreamClass desc,
|
ObjectStreamClass desc) {
|
||||||
ObjectInputStream.FieldValues fieldValues) {
|
ObjectStreamField[] fields = desc.getFields(false);
|
||||||
ObjectStreamField[] fields = desc.getFields();
|
|
||||||
|
|
||||||
for (int i = 0; i < fields.length; i++) {
|
for (int i = 0; i < fields.length; i++) {
|
||||||
ObjectStreamField f = fields[i];
|
ObjectStreamField f = fields[i];
|
||||||
@ -2612,30 +2744,62 @@ public class ObjectStreamClass implements Serializable {
|
|||||||
throw new InternalError(fName + " unassignable, pType:" + pType + ", fType:" + fType);
|
throw new InternalError(fName + " unassignable, pType:" + pType + ", fType:" + fType);
|
||||||
|
|
||||||
if (f.isPrimitive()) {
|
if (f.isPrimitive()) {
|
||||||
if (pType == Integer.TYPE)
|
// (byte[], int):fType
|
||||||
return Bits.getInt(fieldValues.primValues, f.getOffset());
|
MethodHandle mh = PRIM_VALUE_EXTRACTORS.get(fType);
|
||||||
else if (fType == Byte.TYPE)
|
if (mh == null) {
|
||||||
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
|
|
||||||
throw new InternalError("Unexpected type: " + fType);
|
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
|
} 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…
x
Reference in New Issue
Block a user