From a3c151bad7a798ae1921663d6c240f6afa897249 Mon Sep 17 00:00:00 2001 From: lifeinwild Date: Wed, 26 Jan 2022 12:20:10 +0900 Subject: [PATCH 01/20] ArrayClassResolver This modification provides a faster lookup from classID to Registration. --- .../ArrayClassResolverBenchmark.java | 196 ++++++++++++++++++ .../benchmarks/FieldSerializerBenchmark.java | 16 +- .../kryo/benchmarks/KryoBenchmarks.java | 2 +- .../kryo/util/ArrayClassResolver.java | 109 ++++++++++ .../esotericsoftware/kryo/KryoTestCase.java | 2 + .../kryo/util/ArrayClassResolverTest.java | 53 +++++ 6 files changed, 376 insertions(+), 2 deletions(-) create mode 100644 benchmarks/src/main/java/com/esotericsoftware/kryo/benchmarks/ArrayClassResolverBenchmark.java create mode 100644 src/com/esotericsoftware/kryo/util/ArrayClassResolver.java create mode 100644 test/com/esotericsoftware/kryo/util/ArrayClassResolverTest.java diff --git a/benchmarks/src/main/java/com/esotericsoftware/kryo/benchmarks/ArrayClassResolverBenchmark.java b/benchmarks/src/main/java/com/esotericsoftware/kryo/benchmarks/ArrayClassResolverBenchmark.java new file mode 100644 index 000000000..1c8982b4b --- /dev/null +++ b/benchmarks/src/main/java/com/esotericsoftware/kryo/benchmarks/ArrayClassResolverBenchmark.java @@ -0,0 +1,196 @@ +/* Copyright (c) 2008-2020, Nathan Sweet + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following + * conditions are met: + * + * - Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. + * - Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following + * disclaimer in the documentation and/or other materials provided with the distribution. + * - Neither the name of Esoteric Software nor the names of its contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, + * BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT + * SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ + +package com.esotericsoftware.kryo.benchmarks; + +import com.esotericsoftware.kryo.Kryo; +import com.esotericsoftware.kryo.benchmarks.data.Sample; +import com.esotericsoftware.kryo.io.Input; +import com.esotericsoftware.kryo.io.Output; +import com.esotericsoftware.kryo.util.ArrayClassResolver; +import org.openjdk.jmh.annotations.*; + +import java.util.*; + +import static com.esotericsoftware.kryo.benchmarks.FieldSerializerBenchmark.*; + +/** + * {@link ArrayClassResolver} is fast especially in {@link #deserializeCollection(DeserializeCollectionStateArray)}. + * + * @author lifeinwild1@gmail.com + */ +public class ArrayClassResolverBenchmark { + @Benchmark + public void field (FieldSerializerStateArray state) { + state.roundTrip(); + } + + @Benchmark + public void compatible (CompatibleStateArray state) { + state.roundTrip(); + } + + @Benchmark + public void tagged (TaggedStateArray state) { + state.roundTrip(); + } + + @Benchmark + public void version (VersionStateArray state) { + state.roundTrip(); + } + + @Benchmark + public void custom (CustomStateArray state) { + state.roundTrip(); + } + + @Benchmark + public void deserializeCollection(DeserializeCollectionStateArray state) { state.roundTrip(); } + + // + + public static Kryo createKryoArray(){ + Kryo r = new Kryo(new ArrayClassResolver(), null); + return r; + } + + /** + * ad-hoc code for profiling + */ + public static void main(String args[]){ + //ArrayClassResolver is efficient for collection. + Map m = new HashMap<>(); + + //The performance difference is proportional to the size of collection. + int size = 2000; + for(long i=0;i m = new HashMap<>(); + for(long i=0;i<2000;i++) + m.put(i, new Sample().populate(true)); + + object = m; + + kryo.register(HashMap.class); + kryo.register(Sample.class); + + output.setPosition(0); + kryo.writeObject(output, object); + } + + public void roundTrip() { + input.setPosition(0); + input.setLimit(output.position()); + kryo.readObject(input, object.getClass()); + } + } +} diff --git a/benchmarks/src/main/java/com/esotericsoftware/kryo/benchmarks/FieldSerializerBenchmark.java b/benchmarks/src/main/java/com/esotericsoftware/kryo/benchmarks/FieldSerializerBenchmark.java index 5c3394c10..2d6c83693 100644 --- a/benchmarks/src/main/java/com/esotericsoftware/kryo/benchmarks/FieldSerializerBenchmark.java +++ b/benchmarks/src/main/java/com/esotericsoftware/kryo/benchmarks/FieldSerializerBenchmark.java @@ -70,6 +70,9 @@ public void custom (CustomState state) { state.roundTrip(); } + @Benchmark + public void deserializeCollection(DeserializeCollectionStateDefault state) { state.roundTrip(); } + // @State(Scope.Thread) @@ -77,11 +80,15 @@ static public abstract class BenchmarkState { @Param({"true", "false"}) public boolean references; @Param() public ObjectType objectType; - final Kryo kryo = new Kryo(); + final Kryo kryo = createKryo(); final Output output = new Output(1024 * 512); final Input input = new Input(output.getBuffer()); Object object; + public Kryo createKryo(){ + return new Kryo(); + } + @Setup(Level.Trial) public void setup () { switch (objectType) { @@ -310,4 +317,11 @@ public void write (Kryo kryo, Output output, Image image) { kryo.writeObjectOrNull(output, image.media, Media.class); } } + + static public class DeserializeCollectionStateDefault extends ArrayClassResolverBenchmark.DeserializeCollectionState { + @Override + protected Kryo createKryo() { + return new Kryo(); + } + } } diff --git a/benchmarks/src/main/java/com/esotericsoftware/kryo/benchmarks/KryoBenchmarks.java b/benchmarks/src/main/java/com/esotericsoftware/kryo/benchmarks/KryoBenchmarks.java index 004787961..754c8b270 100644 --- a/benchmarks/src/main/java/com/esotericsoftware/kryo/benchmarks/KryoBenchmarks.java +++ b/benchmarks/src/main/java/com/esotericsoftware/kryo/benchmarks/KryoBenchmarks.java @@ -27,7 +27,7 @@ public class KryoBenchmarks { * Fork 0 can be used for debugging/development, eg: -f 0 -wi 1 -i 1 -t 1 -w 1s -r 1s [benchmarkClassName] */ static public void main (String[] args) throws Exception { if (args.length == 0) { - String commandLine = "-f 0 -wi 1 -i 1 -t 1 -w 1s -r 1s " // For developement only (fork 0, short runs). + String commandLine = "-f 0 -wi 2 -i 1 -t 1 -w 1s -r 1s " // For developement only (fork 0, short runs). // + "-bs 2500000 ArrayBenchmark" // // + "-rf csv FieldSerializerBenchmark.field FieldSerializerBenchmark.tagged" // // + "FieldSerializerBenchmark.tagged" // diff --git a/src/com/esotericsoftware/kryo/util/ArrayClassResolver.java b/src/com/esotericsoftware/kryo/util/ArrayClassResolver.java new file mode 100644 index 000000000..9a6a7fa4d --- /dev/null +++ b/src/com/esotericsoftware/kryo/util/ArrayClassResolver.java @@ -0,0 +1,109 @@ +/* Copyright (c) 2008-2020, Nathan Sweet + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following + * conditions are met: + * + * - Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. + * - Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following + * disclaimer in the documentation and/or other materials provided with the distribution. + * - Neither the name of Esoteric Software nor the names of its contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, + * BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT + * SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ + +package com.esotericsoftware.kryo.util; + +import com.esotericsoftware.kryo.Kryo; +import com.esotericsoftware.kryo.KryoException; +import com.esotericsoftware.kryo.Registration; +import com.esotericsoftware.kryo.io.Input; + +import static com.esotericsoftware.kryo.util.Util.*; +import static com.esotericsoftware.minlog.Log.*; + +/** This is enhanced resolver from {@link DefaultClassResolver} for fast deserialization of collections only in + * {@link Kryo#registrationRequired} == true. You can specify the mappings between class and ID by + * {@link Kryo#register(Class, int)}, But don't specify huge ID like 20000000 because this resolver uses array internally. This + * resolver internally reconstructs {@link #idToRegistrationArray} whenever the mappings are updated, by + * {@link #updateIdToRegistrationArray()}. Therefore, it is not suitable in terms of performance if the mappings are updated + * frequently at peaktime of application. In terms of functionality, {@link ArrayClassResolver} is completely equivalent to + * {@link DefaultClassResolver}. So output binary of {@link ArrayClassResolver} is equivalent to that of + * {@link DefaultClassResolver}. + * + * @author lifeinwild1@gmail.com */ +public final class ArrayClassResolver extends DefaultClassResolver { + /** array variant of {@link DefaultClassResolver#idToRegistration} for fast lookup. */ + private Registration[] idToRegistrationArray = new Registration[0]; + + private void updateIdToRegistrationArray () { + int maxId = 0; + for (Registration e : idToRegistration.values()) { + if (e.getId() > maxId) + maxId = e.getId(); + } + + Registration[] updated = new Registration[maxId + 1]; + for (Registration e : idToRegistration.values()) { + updated[e.getId()] = e; + } + + idToRegistrationArray = updated; + } + + @Override + public final Registration getRegistration (int classID) { + if (classID >= idToRegistrationArray.length) + return null; + return idToRegistrationArray[classID]; + } + + @Override + public Registration readClass (Input input) { + int classID = input.readVarInt(true); + switch (classID) { + case Kryo.NULL: + if (TRACE || (DEBUG && kryo.getDepth() == 1)) log("Read", null, input.position()); + return null; + case NAME + 2: // Offset for NAME and NULL. + return readName(input); + } + int index = classID - 2; + Registration registration = getRegistration(index); + if (registration == null) throw new KryoException("Encountered unregistered class ID: " + (classID - 2)); + if (TRACE) trace("kryo", "Read class " + (classID - 2) + ": " + className(registration.getType()) + pos(input.position())); + return registration; + } + + /* + * @Override public void reset() { super.reset(); //This method does not reset the entire state of ClassResolver, but resets + * states that is created per object graph. //So the mappings between class and id is not reset. //The semantic is actually + * like resetPerUse(). } + */ + + @Override + public Registration unregister (int classID) { + Registration r = super.unregister(classID); + updateIdToRegistrationArray(); + return r; + } + + @Override + public Registration register (Registration registration) { + Registration r = super.register(registration); + updateIdToRegistrationArray(); + return r; + } + + @Override + public Registration registerImplicit (Class type) { + Registration r = super.registerImplicit(type); + updateIdToRegistrationArray(); + return r; + } +} diff --git a/test/com/esotericsoftware/kryo/KryoTestCase.java b/test/com/esotericsoftware/kryo/KryoTestCase.java index fd06e1064..514b103b6 100644 --- a/test/com/esotericsoftware/kryo/KryoTestCase.java +++ b/test/com/esotericsoftware/kryo/KryoTestCase.java @@ -40,6 +40,7 @@ import java.nio.ByteBuffer; import java.util.ArrayList; +import com.esotericsoftware.kryo.util.ArrayClassResolver; import org.junit.jupiter.api.BeforeEach; /** Convenience methods for round tripping objects. @@ -71,6 +72,7 @@ public void setUp () throws Exception { if (debug && WARN) warn("*** DEBUG TEST ***"); kryo = new Kryo(); + //kryo = new Kryo(new ArrayClassResolver(), null); } /** @param lengthGenerics Pass Integer.MIN_VALUE to disable checking the length for the generic serialization. diff --git a/test/com/esotericsoftware/kryo/util/ArrayClassResolverTest.java b/test/com/esotericsoftware/kryo/util/ArrayClassResolverTest.java new file mode 100644 index 000000000..01575ee8a --- /dev/null +++ b/test/com/esotericsoftware/kryo/util/ArrayClassResolverTest.java @@ -0,0 +1,53 @@ +package com.esotericsoftware.kryo.util; + +import com.esotericsoftware.kryo.Kryo; +import com.esotericsoftware.kryo.KryoTestCase; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import java.util.ArrayList; +import java.util.HashMap; + +import static org.junit.jupiter.api.Assertions.*; + +/** + * This class has only few test, but I tested {@link ArrayClassResolver} fully by below method: + * I temporarily modified the kryo instance of {@link KryoTestCase#setUp()}, then all test cases passed. + * + *
+ * @BeforeEach
+ * public void setUp () throws Exception {
+ *	 if (debug && WARN) warn("*** DEBUG TEST ***");
+ *
+ * 	 kryo = new Kryo(new ArrayClassResolver(), null);
+ * }
+ *
+ * Tests passed: 267 of 267 tests
+ * 
+ * + * @author lifeinwild1@gmail.com + */ +public class ArrayClassResolverTest extends KryoTestCase { + @BeforeEach + public void setUp () throws Exception { + super.setUp(); + + ArrayClassResolver resolver = new ArrayClassResolver(); + kryo = new Kryo(resolver, null); + kryo.register(ArrayList.class); + } + + @Test + void testBasic () { + ArrayList test = new ArrayList(); + test.add("one"); + test.add("two"); + test.add("three"); + + ArrayList copy = kryo.copy(test); + assertNotSame(test, copy); + assertEquals(test, copy); + } + + +} From 59ec7dbba16e18a7d611e8a51eac5b6ae8541106 Mon Sep 17 00:00:00 2001 From: lifeinwild Date: Wed, 26 Jan 2022 13:19:16 +0900 Subject: [PATCH 02/20] Update ArrayClassResolverBenchmark.java A fix because an error occured after checkout latest remote version. And by changing Sample to String in the value of Map, the performance difference of ClassResolver is more clear in the score. --- .../ArrayClassResolverBenchmark.java | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/benchmarks/src/main/java/com/esotericsoftware/kryo/benchmarks/ArrayClassResolverBenchmark.java b/benchmarks/src/main/java/com/esotericsoftware/kryo/benchmarks/ArrayClassResolverBenchmark.java index 1c8982b4b..07cb3b046 100644 --- a/benchmarks/src/main/java/com/esotericsoftware/kryo/benchmarks/ArrayClassResolverBenchmark.java +++ b/benchmarks/src/main/java/com/esotericsoftware/kryo/benchmarks/ArrayClassResolverBenchmark.java @@ -20,7 +20,6 @@ package com.esotericsoftware.kryo.benchmarks; import com.esotericsoftware.kryo.Kryo; -import com.esotericsoftware.kryo.benchmarks.data.Sample; import com.esotericsoftware.kryo.io.Input; import com.esotericsoftware.kryo.io.Output; import com.esotericsoftware.kryo.util.ArrayClassResolver; @@ -33,6 +32,13 @@ /** * {@link ArrayClassResolver} is fast especially in {@link #deserializeCollection(DeserializeCollectionStateArray)}. * + *
+ * new module/old module=4125/3511=17% faster in a environment.
+ * Benchmark                                           Mode  Cnt     Score   Error  Units
+ * ArrayClassResolverBenchmark.deserializeCollection  thrpt       4125.996          ops/s
+ * FieldSerializerBenchmark.deserializeCollection     thrpt       3511.014          ops/s
+ * 
+ * * @author lifeinwild1@gmail.com */ public class ArrayClassResolverBenchmark { @@ -67,8 +73,7 @@ public void custom (CustomStateArray state) { // public static Kryo createKryoArray(){ - Kryo r = new Kryo(new ArrayClassResolver(), null); - return r; + return new Kryo(new ArrayClassResolver(), null); } /** @@ -107,7 +112,7 @@ public static void main(String args[]){ int loop2 = 1000 * 1000; for(int i=0;i deserialized = (HashMap)k.readClassAndObject(in); if(!m.equals(deserialized)){ System.out.println("error"); } @@ -174,14 +179,13 @@ static public abstract class DeserializeCollectionState{ @Setup(Level.Trial) public void setup () { - HashMap m = new HashMap<>(); + HashMap m = new HashMap<>(); for(long i=0;i<2000;i++) - m.put(i, new Sample().populate(true)); + m.put(i, "val"); object = m; kryo.register(HashMap.class); - kryo.register(Sample.class); output.setPosition(0); kryo.writeObject(output, object); From 0befbd19d41256c25775eca6256b59ae0cbcbcaa Mon Sep 17 00:00:00 2001 From: lifeinwild Date: Wed, 26 Jan 2022 14:32:54 +0900 Subject: [PATCH 03/20] copyright following https://github.com/EsotericSoftware/kryo/commit/180729e61f417b5a1768a4f7d045866dc9abc3e2#diff-6d4638ca49aa0d0d9171ff04a0faa22e241f8320fda4a8a12c95853188d055a0 --- .../ArrayClassResolverBenchmark.java | 2 +- .../kryo/util/ArrayClassResolver.java | 2 +- .../kryo/util/ArrayClassResolverTest.java | 19 +++++++++++++++++++ 3 files changed, 21 insertions(+), 2 deletions(-) diff --git a/benchmarks/src/main/java/com/esotericsoftware/kryo/benchmarks/ArrayClassResolverBenchmark.java b/benchmarks/src/main/java/com/esotericsoftware/kryo/benchmarks/ArrayClassResolverBenchmark.java index 07cb3b046..3c1144292 100644 --- a/benchmarks/src/main/java/com/esotericsoftware/kryo/benchmarks/ArrayClassResolverBenchmark.java +++ b/benchmarks/src/main/java/com/esotericsoftware/kryo/benchmarks/ArrayClassResolverBenchmark.java @@ -1,4 +1,4 @@ -/* Copyright (c) 2008-2020, Nathan Sweet +/* Copyright (c) 2008-2022, Nathan Sweet * All rights reserved. * * Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following diff --git a/src/com/esotericsoftware/kryo/util/ArrayClassResolver.java b/src/com/esotericsoftware/kryo/util/ArrayClassResolver.java index 9a6a7fa4d..cb974be96 100644 --- a/src/com/esotericsoftware/kryo/util/ArrayClassResolver.java +++ b/src/com/esotericsoftware/kryo/util/ArrayClassResolver.java @@ -1,4 +1,4 @@ -/* Copyright (c) 2008-2020, Nathan Sweet +/* Copyright (c) 2008-2022, Nathan Sweet * All rights reserved. * * Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following diff --git a/test/com/esotericsoftware/kryo/util/ArrayClassResolverTest.java b/test/com/esotericsoftware/kryo/util/ArrayClassResolverTest.java index 01575ee8a..1eab28cf0 100644 --- a/test/com/esotericsoftware/kryo/util/ArrayClassResolverTest.java +++ b/test/com/esotericsoftware/kryo/util/ArrayClassResolverTest.java @@ -1,3 +1,22 @@ +/* Copyright (c) 2008-2022, Nathan Sweet + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following + * conditions are met: + * + * - Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. + * - Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following + * disclaimer in the documentation and/or other materials provided with the distribution. + * - Neither the name of Esoteric Software nor the names of its contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, + * BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT + * SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ + package com.esotericsoftware.kryo.util; import com.esotericsoftware.kryo.Kryo; From 05c9bc2f21717f07a3eb3e57f2af6af0ddaf6206 Mon Sep 17 00:00:00 2001 From: lifeinwild Date: Thu, 27 Jan 2022 07:07:43 +0900 Subject: [PATCH 04/20] memoizedClass in ArrayClassResolver discussion https://github.com/EsotericSoftware/kryo/pull/878 --- .../kryo/util/ArrayClassResolver.java | 13 +++++++++++++ .../kryo/util/DefaultClassResolver.java | 8 ++++---- 2 files changed, 17 insertions(+), 4 deletions(-) diff --git a/src/com/esotericsoftware/kryo/util/ArrayClassResolver.java b/src/com/esotericsoftware/kryo/util/ArrayClassResolver.java index cb974be96..547ab9bc9 100644 --- a/src/com/esotericsoftware/kryo/util/ArrayClassResolver.java +++ b/src/com/esotericsoftware/kryo/util/ArrayClassResolver.java @@ -73,10 +73,23 @@ public Registration readClass (Input input) { case NAME + 2: // Offset for NAME and NULL. return readName(input); } + + // check cache + if (classID == memoizedClassId) { + if (TRACE) trace("kryo", + "Read class " + (classID - 2) + ": " + className(memoizedClassIdValue.getType()) + pos(input.position())); + return memoizedClassIdValue; + } + int index = classID - 2; Registration registration = getRegistration(index); if (registration == null) throw new KryoException("Encountered unregistered class ID: " + (classID - 2)); if (TRACE) trace("kryo", "Read class " + (classID - 2) + ": " + className(registration.getType()) + pos(input.position())); + + // set cache + memoizedClassId = classID; + memoizedClassIdValue = registration; + return registration; } diff --git a/src/com/esotericsoftware/kryo/util/DefaultClassResolver.java b/src/com/esotericsoftware/kryo/util/DefaultClassResolver.java index 972c0e536..680f203a1 100644 --- a/src/com/esotericsoftware/kryo/util/DefaultClassResolver.java +++ b/src/com/esotericsoftware/kryo/util/DefaultClassResolver.java @@ -44,10 +44,10 @@ public class DefaultClassResolver implements ClassResolver { protected ObjectMap nameToClass; protected int nextNameId; - private int memoizedClassId = -1; - private Registration memoizedClassIdValue; - private Class memoizedClass; - private Registration memoizedClassValue; + protected int memoizedClassId = -1; + protected Registration memoizedClassIdValue; + protected Class memoizedClass; + protected Registration memoizedClassValue; public void setKryo (Kryo kryo) { this.kryo = kryo; From 8eee727d8aed0c6bfb51ca3613d48e54b9bb6fef Mon Sep 17 00:00:00 2001 From: lifeinwild Date: Thu, 27 Jan 2022 08:25:08 +0900 Subject: [PATCH 05/20] Improved javadoc and about Pooling To recommend Pooling for a specific risk of ArrayClassResolver. https://github.com/EsotericSoftware/kryo/pull/878 --- .../kryo/util/ArrayClassResolver.java | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/src/com/esotericsoftware/kryo/util/ArrayClassResolver.java b/src/com/esotericsoftware/kryo/util/ArrayClassResolver.java index 547ab9bc9..cfa048c89 100644 --- a/src/com/esotericsoftware/kryo/util/ArrayClassResolver.java +++ b/src/com/esotericsoftware/kryo/util/ArrayClassResolver.java @@ -27,14 +27,12 @@ import static com.esotericsoftware.kryo.util.Util.*; import static com.esotericsoftware.minlog.Log.*; -/** This is enhanced resolver from {@link DefaultClassResolver} for fast deserialization of collections only in - * {@link Kryo#registrationRequired} == true. You can specify the mappings between class and ID by - * {@link Kryo#register(Class, int)}, But don't specify huge ID like 20000000 because this resolver uses array internally. This - * resolver internally reconstructs {@link #idToRegistrationArray} whenever the mappings are updated, by - * {@link #updateIdToRegistrationArray()}. Therefore, it is not suitable in terms of performance if the mappings are updated - * frequently at peaktime of application. In terms of functionality, {@link ArrayClassResolver} is completely equivalent to - * {@link DefaultClassResolver}. So output binary of {@link ArrayClassResolver} is equivalent to that of - * {@link DefaultClassResolver}. +/** This is enhanced resolver from {@link DefaultClassResolver} for fast deserialization of collections only in configuration {@link Kryo#registrationRequired} == true. + * + * @apiNote In terms of functionality, {@link ArrayClassResolver} is completely equivalent to {@link DefaultClassResolver}. So output binary of {@link ArrayClassResolver} is equivalent to that of {@link DefaultClassResolver}. + * + * @implNote You can specify the mappings between class and ID by {@link Kryo#register(Class, int)}, But don't specify huge ID like 20000000 because this resolver uses array internally. This resolver internally reconstructs {@link #idToRegistrationArray} whenever the mappings are updated, by {@link #updateIdToRegistrationArray()}. Therefore, it is not suitable in terms of performance if the mappings are updated frequently at peaktime of application. Use the {@link Pool}. + * @see Pool * * @author lifeinwild1@gmail.com */ public final class ArrayClassResolver extends DefaultClassResolver { From 24c49659e0576dc3df7f76fe8292af6963bb49f6 Mon Sep 17 00:00:00 2001 From: lifeinwild Date: Thu, 27 Jan 2022 08:33:47 +0900 Subject: [PATCH 06/20] Forgot to use formatter. --- .../kryo/util/ArrayClassResolver.java | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/src/com/esotericsoftware/kryo/util/ArrayClassResolver.java b/src/com/esotericsoftware/kryo/util/ArrayClassResolver.java index cfa048c89..ee572c379 100644 --- a/src/com/esotericsoftware/kryo/util/ArrayClassResolver.java +++ b/src/com/esotericsoftware/kryo/util/ArrayClassResolver.java @@ -27,11 +27,17 @@ import static com.esotericsoftware.kryo.util.Util.*; import static com.esotericsoftware.minlog.Log.*; -/** This is enhanced resolver from {@link DefaultClassResolver} for fast deserialization of collections only in configuration {@link Kryo#registrationRequired} == true. +/** This is enhanced resolver from {@link DefaultClassResolver} for fast deserialization of collections only in configuration + * {@link Kryo#registrationRequired} == true. * - * @apiNote In terms of functionality, {@link ArrayClassResolver} is completely equivalent to {@link DefaultClassResolver}. So output binary of {@link ArrayClassResolver} is equivalent to that of {@link DefaultClassResolver}. + * @apiNote In terms of functionality, {@link ArrayClassResolver} is completely equivalent to {@link DefaultClassResolver}. So + * output binary of {@link ArrayClassResolver} is equivalent to that of {@link DefaultClassResolver}. * - * @implNote You can specify the mappings between class and ID by {@link Kryo#register(Class, int)}, But don't specify huge ID like 20000000 because this resolver uses array internally. This resolver internally reconstructs {@link #idToRegistrationArray} whenever the mappings are updated, by {@link #updateIdToRegistrationArray()}. Therefore, it is not suitable in terms of performance if the mappings are updated frequently at peaktime of application. Use the {@link Pool}. + * @implNote You can specify the mappings between class and ID by {@link Kryo#register(Class, int)}, But don't specify huge ID + * like 20000000 because this resolver uses array internally. This resolver internally reconstructs + * {@link #idToRegistrationArray} whenever the mappings are updated, by {@link #updateIdToRegistrationArray()}. + * Therefore, it is not suitable in terms of performance if the mappings are updated frequently at peaktime of + * application. Use the {@link Pool}. * @see Pool * * @author lifeinwild1@gmail.com */ From ef47a8b0bc1b11778e7d269b4731708f5d914823 Mon Sep 17 00:00:00 2001 From: lifeinwild Date: Fri, 28 Jan 2022 01:34:12 +0900 Subject: [PATCH 07/20] Minimize changes to existing files --- .../com/esotericsoftware/kryo/benchmarks/KryoBenchmarks.java | 2 +- test/com/esotericsoftware/kryo/KryoTestCase.java | 1 - 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/benchmarks/src/main/java/com/esotericsoftware/kryo/benchmarks/KryoBenchmarks.java b/benchmarks/src/main/java/com/esotericsoftware/kryo/benchmarks/KryoBenchmarks.java index 8379336d2..d6e268ca1 100644 --- a/benchmarks/src/main/java/com/esotericsoftware/kryo/benchmarks/KryoBenchmarks.java +++ b/benchmarks/src/main/java/com/esotericsoftware/kryo/benchmarks/KryoBenchmarks.java @@ -27,7 +27,7 @@ public class KryoBenchmarks { * Fork 0 can be used for debugging/development, eg: -f 0 -wi 1 -i 1 -t 1 -w 1s -r 1s [benchmarkClassName] */ static public void main (String[] args) throws Exception { if (args.length == 0) { - String commandLine = "-f 0 -wi 2 -i 1 -t 1 -w 1s -r 1s " // For developement only (fork 0, short runs). + String commandLine = "-f 0 -wi 1 -i 1 -t 1 -w 1s -r 1s " // For developement only (fork 0, short runs). // + "-bs 2500000 ArrayBenchmark" // // + "-rf csv FieldSerializerBenchmark.field FieldSerializerBenchmark.tagged" // // + "FieldSerializerBenchmark.tagged" // diff --git a/test/com/esotericsoftware/kryo/KryoTestCase.java b/test/com/esotericsoftware/kryo/KryoTestCase.java index 64b62e467..1d12eb31b 100644 --- a/test/com/esotericsoftware/kryo/KryoTestCase.java +++ b/test/com/esotericsoftware/kryo/KryoTestCase.java @@ -72,7 +72,6 @@ public void setUp () throws Exception { if (debug && WARN) warn("*** DEBUG TEST ***"); kryo = new Kryo(); - //kryo = new Kryo(new ArrayClassResolver(), null); } /** @param lengthGenerics Pass Integer.MIN_VALUE to disable checking the length for the generic serialization. From 6ec1b5039c212c958eb2a4b6235cff74fa79f5d5 Mon Sep 17 00:00:00 2001 From: lifeinwild Date: Fri, 28 Jan 2022 01:34:27 +0900 Subject: [PATCH 08/20] delete comment --- src/com/esotericsoftware/kryo/util/ArrayClassResolver.java | 6 ------ 1 file changed, 6 deletions(-) diff --git a/src/com/esotericsoftware/kryo/util/ArrayClassResolver.java b/src/com/esotericsoftware/kryo/util/ArrayClassResolver.java index ee572c379..67d64ae73 100644 --- a/src/com/esotericsoftware/kryo/util/ArrayClassResolver.java +++ b/src/com/esotericsoftware/kryo/util/ArrayClassResolver.java @@ -97,12 +97,6 @@ public Registration readClass (Input input) { return registration; } - /* - * @Override public void reset() { super.reset(); //This method does not reset the entire state of ClassResolver, but resets - * states that is created per object graph. //So the mappings between class and id is not reset. //The semantic is actually - * like resetPerUse(). } - */ - @Override public Registration unregister (int classID) { Registration r = super.unregister(classID); From 1e3898f5c81b04ad5629d19055b6b09df3ced9ae Mon Sep 17 00:00:00 2001 From: lifeinwild Date: Fri, 28 Jan 2022 01:40:55 +0900 Subject: [PATCH 09/20] delete unused import --- test/com/esotericsoftware/kryo/KryoTestCase.java | 1 - 1 file changed, 1 deletion(-) diff --git a/test/com/esotericsoftware/kryo/KryoTestCase.java b/test/com/esotericsoftware/kryo/KryoTestCase.java index 1d12eb31b..1cf7dcd2d 100644 --- a/test/com/esotericsoftware/kryo/KryoTestCase.java +++ b/test/com/esotericsoftware/kryo/KryoTestCase.java @@ -40,7 +40,6 @@ import java.nio.ByteBuffer; import java.util.ArrayList; -import com.esotericsoftware.kryo.util.ArrayClassResolver; import org.junit.jupiter.api.BeforeEach; /** Convenience methods for round tripping objects. From 6d793e73278dae77e70a80f09c90e4872bd7915f Mon Sep 17 00:00:00 2001 From: lifeinwild Date: Fri, 28 Jan 2022 01:52:23 +0900 Subject: [PATCH 10/20] rename BenchmarkState classes --- .../kryo/benchmarks/ArrayClassResolverBenchmark.java | 8 ++++---- .../kryo/benchmarks/FieldSerializerBenchmark.java | 4 ++-- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/benchmarks/src/main/java/com/esotericsoftware/kryo/benchmarks/ArrayClassResolverBenchmark.java b/benchmarks/src/main/java/com/esotericsoftware/kryo/benchmarks/ArrayClassResolverBenchmark.java index 3c1144292..e0de25c3e 100644 --- a/benchmarks/src/main/java/com/esotericsoftware/kryo/benchmarks/ArrayClassResolverBenchmark.java +++ b/benchmarks/src/main/java/com/esotericsoftware/kryo/benchmarks/ArrayClassResolverBenchmark.java @@ -30,7 +30,7 @@ import static com.esotericsoftware.kryo.benchmarks.FieldSerializerBenchmark.*; /** - * {@link ArrayClassResolver} is fast especially in {@link #deserializeCollection(DeserializeCollectionStateArray)}. + * {@link ArrayClassResolver} is fast especially in {@link #deserializeCollection(DeserializingCollectionWithArrayClassResolverState)}}. * *
  * new module/old module=4125/3511=17% faster in a environment.
@@ -68,7 +68,7 @@ public void custom (CustomStateArray state) {
 	}
 
 	@Benchmark
-	public void deserializeCollection(DeserializeCollectionStateArray state) { state.roundTrip(); }
+	public void deserializeCollection(DeserializingCollectionWithArrayClassResolverState state) { state.roundTrip(); }
 
 	//
 
@@ -161,7 +161,7 @@ public Kryo createKryo() {
 		}
 	}
 
-	static public class DeserializeCollectionStateArray extends DeserializeCollectionState {
+	static public class DeserializingCollectionWithArrayClassResolverState extends DeserializingCollectionState {
 		@Override
 		protected Kryo createKryo() {
 			return createKryoArray();
@@ -169,7 +169,7 @@ protected Kryo createKryo() {
 	}
 
 	@State(Scope.Thread)
-	static public abstract class DeserializeCollectionState{
+	static public abstract class DeserializingCollectionState{
 		final Kryo kryo = createKryo();
 		final Output output = new Output(1024 * 1024);
 		final Input input = new Input(output.getBuffer());
diff --git a/benchmarks/src/main/java/com/esotericsoftware/kryo/benchmarks/FieldSerializerBenchmark.java b/benchmarks/src/main/java/com/esotericsoftware/kryo/benchmarks/FieldSerializerBenchmark.java
index de71ac3cf..41ff0a50c 100644
--- a/benchmarks/src/main/java/com/esotericsoftware/kryo/benchmarks/FieldSerializerBenchmark.java
+++ b/benchmarks/src/main/java/com/esotericsoftware/kryo/benchmarks/FieldSerializerBenchmark.java
@@ -71,7 +71,7 @@ public void custom (CustomState state) {
 	}
 
 	@Benchmark
-	public void deserializeCollection(DeserializeCollectionStateDefault state) { state.roundTrip(); }
+	public void deserializeCollection(DeserializingCollectionWithDefaultClassResolverState state) { state.roundTrip(); }
 
 	//
 
@@ -318,7 +318,7 @@ public void write (Kryo kryo, Output output, Image image) {
 		}
 	}
 
-	static public class DeserializeCollectionStateDefault extends ArrayClassResolverBenchmark.DeserializeCollectionState {
+	static public class DeserializingCollectionWithDefaultClassResolverState extends ArrayClassResolverBenchmark.DeserializingCollectionState {
 		@Override
 		protected Kryo createKryo() {
 			return new Kryo();

From 68d274681cd9abf77fa8fb176ce8430116441775 Mon Sep 17 00:00:00 2001
From: lifeinwild 
Date: Sun, 30 Jan 2022 02:18:05 +0900
Subject: [PATCH 11/20] Significant reduction in the reconstruction of array.

---
 .../kryo/util/ArrayClassResolver.java         | 205 +++++++++++++++---
 .../kryo/util/DefaultClassResolver.java       |   8 +-
 2 files changed, 174 insertions(+), 39 deletions(-)

diff --git a/src/com/esotericsoftware/kryo/util/ArrayClassResolver.java b/src/com/esotericsoftware/kryo/util/ArrayClassResolver.java
index 67d64ae73..bf027dbc3 100644
--- a/src/com/esotericsoftware/kryo/util/ArrayClassResolver.java
+++ b/src/com/esotericsoftware/kryo/util/ArrayClassResolver.java
@@ -19,10 +19,12 @@
 
 package com.esotericsoftware.kryo.util;
 
+import com.esotericsoftware.kryo.ClassResolver;
 import com.esotericsoftware.kryo.Kryo;
 import com.esotericsoftware.kryo.KryoException;
 import com.esotericsoftware.kryo.Registration;
 import com.esotericsoftware.kryo.io.Input;
+import com.esotericsoftware.kryo.io.Output;
 
 import static com.esotericsoftware.kryo.util.Util.*;
 import static com.esotericsoftware.minlog.Log.*;
@@ -35,36 +37,151 @@
  *
  * @implNote You can specify the mappings between class and ID by {@link Kryo#register(Class, int)}, But don't specify huge ID
  *           like 20000000 because this resolver uses array internally. This resolver internally reconstructs
- *           {@link #idToRegistrationArray} whenever the mappings are updated, by {@link #updateIdToRegistrationArray()}.
- *           Therefore, it is not suitable in terms of performance if the mappings are updated frequently at peaktime of
- *           application. Use the {@link Pool}.
+ *           {@link IdToRegistrationArray#array} when the mappings are updated. Therefore, it is not suitable in terms of
+ *           performance if the mappings are updated frequently at peaktime of application. Use the {@link Pool}.
  * @see Pool
  *
  * @author lifeinwild1@gmail.com */
-public final class ArrayClassResolver extends DefaultClassResolver {
-	/** array variant of {@link DefaultClassResolver#idToRegistration} for fast lookup. */
-	private Registration[] idToRegistrationArray = new Registration[0];
-
-	private void updateIdToRegistrationArray () {
-		int maxId = 0;
-		for (Registration e : idToRegistration.values()) {
-			if (e.getId() > maxId)
-				maxId = e.getId();
+public final class ArrayClassResolver implements ClassResolver {
+	protected Kryo kryo;
+
+	protected final IdentityMap classToRegistration = new IdentityMap<>();
+
+	protected IdentityObjectIntMap classToNameId;
+	/** TODO: array */
+	protected IntMap nameIdToClass;
+	protected ObjectMap nameToClass;
+	protected int nextNameId;
+
+	/** TODO: Map support */
+	private int memoizedClassId = -1;
+	private Registration memoizedClassIdValue;
+	private Class memoizedClass;
+	private Registration memoizedClassValue;
+
+	public void setKryo (Kryo kryo) {
+		this.kryo = kryo;
+	}
+
+	public Registration register (Registration registration) {
+		memoizedClassId = -1;
+		memoizedClass = null;
+		if (registration == null) throw new IllegalArgumentException("registration cannot be null.");
+		if (registration.getId() != DefaultClassResolver.NAME) {
+			if (TRACE) {
+				trace("kryo", "Register class ID " + registration.getId() + ": " + className(registration.getType()) + " ("
+					+ registration.getSerializer().getClass().getName() + ")");
+			}
+			idToRegistrationTmp.put(registration.getId(), registration);
+		} else if (TRACE) {
+			trace("kryo", "Register class name: " + className(registration.getType()) + " ("
+				+ registration.getSerializer().getClass().getName() + ")");
+		}
+		classToRegistration.put(registration.getType(), registration);
+		Class wrapperClass = getWrapperClass(registration.getType());
+		if (wrapperClass != registration.getType()) classToRegistration.put(wrapperClass, registration);
+		return registration;
+	}
+
+	public Registration registerImplicit (Class type) {
+		return register(new Registration(type, kryo.getDefaultSerializer(type), DefaultClassResolver.NAME));
+	}
+
+	public Registration getRegistration (Class type) {
+		if (type == memoizedClass) return memoizedClassValue;
+		Registration registration = classToRegistration.get(type);
+		if (registration != null) {
+			memoizedClass = type;
+			memoizedClassValue = registration;
+		}
+		return registration;
+	}
+
+	public Registration writeClass (Output output, Class type) {
+		if (type == null) {
+			if (TRACE || (DEBUG && kryo.getDepth() == 1)) log("Write", null, output.position());
+			output.writeByte(Kryo.NULL);
+			return null;
+		}
+		Registration registration = kryo.getRegistration(type);
+		if (registration.getId() == DefaultClassResolver.NAME)
+			writeName(output, type, registration);
+		else {
+			if (TRACE) trace("kryo", "Write class " + registration.getId() + ": " + className(type) + pos(output.position()));
+			output.writeVarInt(registration.getId() + 2, true);
+		}
+		return registration;
+	}
+
+	protected void writeName (Output output, Class type, Registration registration) {
+		output.writeByte(1); // NAME + 2
+		if (classToNameId != null) {
+			int nameId = classToNameId.get(type, -1);
+			if (nameId != -1) {
+				if (TRACE) trace("kryo", "Write class name reference " + nameId + ": " + className(type) + pos(output.position()));
+				output.writeVarInt(nameId, true);
+				return;
+			}
 		}
+		// Only write the class name the first time encountered in object graph.
+		if (TRACE) trace("kryo", "Write class name: " + className(type) + pos(output.position()));
+		int nameId = nextNameId++;
+		if (classToNameId == null) classToNameId = new IdentityObjectIntMap<>();
+		classToNameId.put(type, nameId);
+		output.writeVarInt(nameId, true);
+		if (registration.isTypeNameAscii())
+			output.writeAscii(type.getName());
+		else
+			output.writeString(type.getName());
+	}
 
-		Registration[] updated = new Registration[maxId + 1];
-		for (Registration e : idToRegistration.values()) {
-			updated[e.getId()] = e;
+	protected Registration readName (Input input) {
+		int nameId = input.readVarInt(true);
+		if (nameIdToClass == null) nameIdToClass = new IntMap<>();
+		Class type = nameIdToClass.get(nameId);
+		if (type == null) {
+			// Only read the class name the first time encountered in object graph.
+			String className = input.readString();
+			type = getTypeByName(className);
+			if (type == null) {
+				try {
+					type = Class.forName(className, false, kryo.getClassLoader());
+				} catch (ClassNotFoundException ex) {
+					// Fallback to Kryo's class loader.
+					try {
+						type = Class.forName(className, false, Kryo.class.getClassLoader());
+					} catch (ClassNotFoundException ex2) {
+						throw new KryoException("Unable to find class: " + className, ex);
+					}
+				}
+				if (nameToClass == null) nameToClass = new ObjectMap<>();
+				nameToClass.put(className, type);
+			}
+			nameIdToClass.put(nameId, type);
+			if (TRACE) trace("kryo", "Read class name: " + className + pos(input.position()));
+		} else {
+			if (TRACE) trace("kryo", "Read class name reference " + nameId + ": " + className(type) + pos(input.position()));
 		}
+		return kryo.getRegistration(type);
+	}
 
-		idToRegistrationArray = updated;
+	protected Class getTypeByName (final String className) {
+		return nameToClass != null ? nameToClass.get(className) : null;
+	}
+
+	public void reset () {
+		if (!kryo.isRegistrationRequired()) {
+			if (classToNameId != null) classToNameId.clear(2048);
+			if (nameIdToClass != null) nameIdToClass.clear();
+			nextNameId = 0;
+		}
 	}
 
 	@Override
 	public final Registration getRegistration (int classID) {
-		if (classID >= idToRegistrationArray.length)
+		if (classID >= idToRegistrationTmp.array.length)
 			return null;
-		return idToRegistrationArray[classID];
+		return idToRegistrationTmp.array[classID];
 	}
 
 	@Override
@@ -74,7 +191,7 @@ public Registration readClass (Input input) {
 		case Kryo.NULL:
 			if (TRACE || (DEBUG && kryo.getDepth() == 1)) log("Read", null, input.position());
 			return null;
-		case NAME + 2: // Offset for NAME and NULL.
+		case DefaultClassResolver.NAME + 2: // Offset for NAME and NULL.
 			return readName(input);
 		}
 
@@ -97,24 +214,42 @@ public Registration readClass (Input input) {
 		return registration;
 	}
 
-	@Override
-	public Registration unregister (int classID) {
-		Registration r = super.unregister(classID);
-		updateIdToRegistrationArray();
-		return r;
-	}
+	private static class IdToRegistrationArray {
+		/** array variant of {@link DefaultClassResolver#idToRegistration} for fast lookup. */
+		public Registration[] array = new Registration[1000];
 
-	@Override
-	public Registration register (Registration registration) {
-		Registration r = super.register(registration);
-		updateIdToRegistrationArray();
-		return r;
+		public Registration remove (int classID) {
+			if (classID >= array.length)
+				return null;
+			Registration r = array[classID];
+			array[classID] = null;
+			return r;
+		}
+
+		public Registration put (int classID, Registration v) {
+			if (classID >= array.length) {
+				Registration[] next = new Registration[(int)(array.length * 1.1)];
+				System.arraycopy(array, 0, next, 0, array.length);
+				array = next;
+			}
+
+			Registration r = array[classID];
+			array[classID] = v;
+			return r;
+		}
 	}
 
-	@Override
-	public Registration registerImplicit (Class type) {
-		Registration r = super.registerImplicit(type);
-		updateIdToRegistrationArray();
-		return r;
+	private IdToRegistrationArray idToRegistrationTmp = new IdToRegistrationArray();
+
+	public Registration unregister (int classID) {
+		Registration registration = idToRegistrationTmp.remove(classID);
+		if (registration != null) {
+			classToRegistration.remove(registration.getType());
+			memoizedClassId = -1;
+			memoizedClass = null;
+			Class wrapperClass = getWrapperClass(registration.getType());
+			if (wrapperClass != registration.getType()) classToRegistration.remove(wrapperClass);
+		}
+		return registration;
 	}
 }
diff --git a/src/com/esotericsoftware/kryo/util/DefaultClassResolver.java b/src/com/esotericsoftware/kryo/util/DefaultClassResolver.java
index 680f203a1..972c0e536 100644
--- a/src/com/esotericsoftware/kryo/util/DefaultClassResolver.java
+++ b/src/com/esotericsoftware/kryo/util/DefaultClassResolver.java
@@ -44,10 +44,10 @@ public class DefaultClassResolver implements ClassResolver {
 	protected ObjectMap nameToClass;
 	protected int nextNameId;
 
-	protected int memoizedClassId = -1;
-	protected Registration memoizedClassIdValue;
-	protected Class memoizedClass;
-	protected Registration memoizedClassValue;
+	private int memoizedClassId = -1;
+	private Registration memoizedClassIdValue;
+	private Class memoizedClass;
+	private Registration memoizedClassValue;
 
 	public void setKryo (Kryo kryo) {
 		this.kryo = kryo;

From 517961d1896187e17d5a0927dccb9afe3a4b6e34 Mon Sep 17 00:00:00 2001
From: lifeinwild 
Date: Sun, 30 Jan 2022 02:47:10 +0900
Subject: [PATCH 12/20] nameIdToClass of array

---
 .../kryo/util/ArrayClassResolver.java         | 59 ++++++++++++-------
 1 file changed, 38 insertions(+), 21 deletions(-)

diff --git a/src/com/esotericsoftware/kryo/util/ArrayClassResolver.java b/src/com/esotericsoftware/kryo/util/ArrayClassResolver.java
index bf027dbc3..344877aee 100644
--- a/src/com/esotericsoftware/kryo/util/ArrayClassResolver.java
+++ b/src/com/esotericsoftware/kryo/util/ArrayClassResolver.java
@@ -26,19 +26,20 @@
 import com.esotericsoftware.kryo.io.Input;
 import com.esotericsoftware.kryo.io.Output;
 
+import java.lang.reflect.Array;
+
 import static com.esotericsoftware.kryo.util.Util.*;
 import static com.esotericsoftware.minlog.Log.*;
 
-/** This is enhanced resolver from {@link DefaultClassResolver} for fast deserialization of collections only in configuration
- * {@link Kryo#registrationRequired} == true.
+/** This is enhanced resolver from {@link DefaultClassResolver} for fast deserialization of collections.
  *
  * @apiNote In terms of functionality, {@link ArrayClassResolver} is completely equivalent to {@link DefaultClassResolver}. So
  *          output binary of {@link ArrayClassResolver} is equivalent to that of {@link DefaultClassResolver}.
  *
  * @implNote You can specify the mappings between class and ID by {@link Kryo#register(Class, int)}, But don't specify huge ID
  *           like 20000000 because this resolver uses array internally. This resolver internally reconstructs
- *           {@link IdToRegistrationArray#array} when the mappings are updated. Therefore, it is not suitable in terms of
- *           performance if the mappings are updated frequently at peaktime of application. Use the {@link Pool}.
+ *           {@link IntToObjArray#array} when the mappings are updated. Therefore, it is not suitable in terms of performance if
+ *           the mappings are added frequently at peaktime of application. Use the {@link Pool}.
  * @see Pool
  *
  * @author lifeinwild1@gmail.com */
@@ -48,8 +49,7 @@ public final class ArrayClassResolver implements ClassResolver {
 	protected final IdentityMap classToRegistration = new IdentityMap<>();
 
 	protected IdentityObjectIntMap classToNameId;
-	/** TODO: array */
-	protected IntMap nameIdToClass;
+	protected IntToObjArray nameIdToClass;
 	protected ObjectMap nameToClass;
 	protected int nextNameId;
 
@@ -137,8 +137,8 @@ protected void writeName (Output output, Class type, Registration registration)
 
 	protected Registration readName (Input input) {
 		int nameId = input.readVarInt(true);
-		if (nameIdToClass == null) nameIdToClass = new IntMap<>();
-		Class type = nameIdToClass.get(nameId);
+		if (nameIdToClass == null) nameIdToClass = new IntToObjArray<>(Class.class);
+		Class type = nameIdToClass.array[nameId];
 		if (type == null) {
 			// Only read the class name the first time encountered in object graph.
 			String className = input.readString();
@@ -214,32 +214,49 @@ public Registration readClass (Input input) {
 		return registration;
 	}
 
-	private static class IdToRegistrationArray {
-		/** array variant of {@link DefaultClassResolver#idToRegistration} for fast lookup. */
-		public Registration[] array = new Registration[1000];
+	/** replacement of {@link IntMap} for faster lookup. */
+	private static class IntToObjArray {
+		public E[] array;
+		private final Class valueType;
+		private final int initialCapacity;
+
+		/** @param valueType for generic type array {@link #array} */
+		IntToObjArray (Class valueType, int initialCapacity) {
+			this.valueType = valueType;
+			array = (E[])Array.newInstance(valueType, initialCapacity);
+			this.initialCapacity = initialCapacity;
+		}
+
+		IntToObjArray (Class valueType) {
+			this(valueType, 1000);
+		}
+
+		public void clear () {
+			array = (E[])Array.newInstance(valueType, initialCapacity);
+		}
 
-		public Registration remove (int classID) {
-			if (classID >= array.length)
+		public E remove (int classid) {
+			if (classid >= array.length)
 				return null;
-			Registration r = array[classID];
-			array[classID] = null;
+			E r = array[classid];
+			array[classid] = null;
 			return r;
 		}
 
-		public Registration put (int classID, Registration v) {
-			if (classID >= array.length) {
-				Registration[] next = new Registration[(int)(array.length * 1.1)];
+		public E put (int classid, E v) {
+			if (classid >= array.length) {
+				E[] next = (E[])Array.newInstance(valueType, (int)(array.length * 1.1));
 				System.arraycopy(array, 0, next, 0, array.length);
 				array = next;
 			}
 
-			Registration r = array[classID];
-			array[classID] = v;
+			E r = array[classid];
+			array[classid] = v;
 			return r;
 		}
 	}
 
-	private IdToRegistrationArray idToRegistrationTmp = new IdToRegistrationArray();
+	private IntToObjArray idToRegistrationTmp = new IntToObjArray(Registration.class);
 
 	public Registration unregister (int classID) {
 		Registration registration = idToRegistrationTmp.remove(classID);

From 58a9a6bd646a7a7374fc066e821a1c423b2003d5 Mon Sep 17 00:00:00 2001
From: lifeinwild 
Date: Sun, 30 Jan 2022 08:42:19 +0900
Subject: [PATCH 13/20] delete comment. move IntToObjArray to new file. small
 initialCapacity. loadFactor

---
 .../kryo/util/ArrayClassResolver.java         | 62 ++--------------
 .../kryo/util/IntToObjArray.java              | 74 +++++++++++++++++++
 2 files changed, 80 insertions(+), 56 deletions(-)
 create mode 100644 src/com/esotericsoftware/kryo/util/IntToObjArray.java

diff --git a/src/com/esotericsoftware/kryo/util/ArrayClassResolver.java b/src/com/esotericsoftware/kryo/util/ArrayClassResolver.java
index 344877aee..260698a90 100644
--- a/src/com/esotericsoftware/kryo/util/ArrayClassResolver.java
+++ b/src/com/esotericsoftware/kryo/util/ArrayClassResolver.java
@@ -26,34 +26,28 @@
 import com.esotericsoftware.kryo.io.Input;
 import com.esotericsoftware.kryo.io.Output;
 
-import java.lang.reflect.Array;
-
 import static com.esotericsoftware.kryo.util.Util.*;
 import static com.esotericsoftware.minlog.Log.*;
 
 /** This is enhanced resolver from {@link DefaultClassResolver} for fast deserialization of collections.
  *
+ * @author lifeinwild1@gmail.com
  * @apiNote In terms of functionality, {@link ArrayClassResolver} is completely equivalent to {@link DefaultClassResolver}. So
  *          output binary of {@link ArrayClassResolver} is equivalent to that of {@link DefaultClassResolver}.
- *
  * @implNote You can specify the mappings between class and ID by {@link Kryo#register(Class, int)}, But don't specify huge ID
  *           like 20000000 because this resolver uses array internally. This resolver internally reconstructs
  *           {@link IntToObjArray#array} when the mappings are updated. Therefore, it is not suitable in terms of performance if
  *           the mappings are added frequently at peaktime of application. Use the {@link Pool}.
- * @see Pool
- *
- * @author lifeinwild1@gmail.com */
-public final class ArrayClassResolver implements ClassResolver {
-	protected Kryo kryo;
-
+ * @see Pool */
+public class ArrayClassResolver implements ClassResolver {
 	protected final IdentityMap classToRegistration = new IdentityMap<>();
-
+	private final IntToObjArray idToRegistrationTmp = new IntToObjArray<>(Registration.class);
+	protected Kryo kryo;
 	protected IdentityObjectIntMap classToNameId;
 	protected IntToObjArray nameIdToClass;
 	protected ObjectMap nameToClass;
 	protected int nextNameId;
 
-	/** TODO: Map support */
 	private int memoizedClassId = -1;
 	private Registration memoizedClassIdValue;
 	private Class memoizedClass;
@@ -137,7 +131,7 @@ protected void writeName (Output output, Class type, Registration registration)
 
 	protected Registration readName (Input input) {
 		int nameId = input.readVarInt(true);
-		if (nameIdToClass == null) nameIdToClass = new IntToObjArray<>(Class.class);
+		if (nameIdToClass == null) nameIdToClass = new IntToObjArray<>(Class.class, 20);
 		Class type = nameIdToClass.array[nameId];
 		if (type == null) {
 			// Only read the class name the first time encountered in object graph.
@@ -214,50 +208,6 @@ public Registration readClass (Input input) {
 		return registration;
 	}
 
-	/** replacement of {@link IntMap} for faster lookup. */
-	private static class IntToObjArray {
-		public E[] array;
-		private final Class valueType;
-		private final int initialCapacity;
-
-		/** @param valueType for generic type array {@link #array} */
-		IntToObjArray (Class valueType, int initialCapacity) {
-			this.valueType = valueType;
-			array = (E[])Array.newInstance(valueType, initialCapacity);
-			this.initialCapacity = initialCapacity;
-		}
-
-		IntToObjArray (Class valueType) {
-			this(valueType, 1000);
-		}
-
-		public void clear () {
-			array = (E[])Array.newInstance(valueType, initialCapacity);
-		}
-
-		public E remove (int classid) {
-			if (classid >= array.length)
-				return null;
-			E r = array[classid];
-			array[classid] = null;
-			return r;
-		}
-
-		public E put (int classid, E v) {
-			if (classid >= array.length) {
-				E[] next = (E[])Array.newInstance(valueType, (int)(array.length * 1.1));
-				System.arraycopy(array, 0, next, 0, array.length);
-				array = next;
-			}
-
-			E r = array[classid];
-			array[classid] = v;
-			return r;
-		}
-	}
-
-	private IntToObjArray idToRegistrationTmp = new IntToObjArray(Registration.class);
-
 	public Registration unregister (int classID) {
 		Registration registration = idToRegistrationTmp.remove(classID);
 		if (registration != null) {
diff --git a/src/com/esotericsoftware/kryo/util/IntToObjArray.java b/src/com/esotericsoftware/kryo/util/IntToObjArray.java
new file mode 100644
index 000000000..0b31924bd
--- /dev/null
+++ b/src/com/esotericsoftware/kryo/util/IntToObjArray.java
@@ -0,0 +1,74 @@
+/* Copyright (c) 2008-2022, Nathan Sweet
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following
+ * conditions are met:
+ *
+ * - Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
+ * - Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following
+ * disclaimer in the documentation and/or other materials provided with the distribution.
+ * - Neither the name of Esoteric Software nor the names of its contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING,
+ * BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT
+ * SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */
+
+package com.esotericsoftware.kryo.util;
+
+import java.lang.reflect.Array;
+
+/** LUT by array.
+ *
+ * @apiNote don't specify large int to key by {@link #put(int, Object)}.
+ * @author lifeinwild1@gmail.com
+ * */
+public final class IntToObjArray {
+	private final Class valueType;
+	private final int initialCapacity;
+	private final float loadFactor;
+	public E[] array;
+
+	/** @param valueType for generic type array {@link #array} */
+	IntToObjArray (Class valueType, int initialCapacity, float loadFactor) {
+		this.valueType = valueType;
+		array = (E[])Array.newInstance(valueType, initialCapacity);
+		this.initialCapacity = initialCapacity;
+		this.loadFactor = loadFactor;
+	}
+
+	IntToObjArray (Class valueType, int initialCapacity) {
+		this(valueType, initialCapacity, 0.75f);
+	}
+
+	IntToObjArray (Class valueType) {
+		this(valueType, 1000);
+	}
+
+	public void clear () {
+		array = (E[])Array.newInstance(valueType, initialCapacity);
+	}
+
+	public E remove (int classid) {
+		if (classid >= array.length)
+			return null;
+		E r = array[classid];
+		array[classid] = null;
+		return r;
+	}
+
+	public E put (int classid, E v) {
+		if (classid >= array.length) {
+			E[] next = (E[])Array.newInstance(valueType, (int)(array.length * (1 + loadFactor)));
+			System.arraycopy(array, 0, next, 0, array.length);
+			array = next;
+		}
+
+		E r = array[classid];
+		array[classid] = v;
+		return r;
+	}
+}

From 989749e1cba629a598db532816b2550682a62158 Mon Sep 17 00:00:00 2001
From: lifeinwild 
Date: Sun, 30 Jan 2022 10:03:13 +0900
Subject: [PATCH 14/20] IntToObjArray#get()

---
 .../kryo/util/ArrayClassResolver.java            | 16 ++++++----------
 .../kryo/util/IntToObjArray.java                 | 13 +++++++++----
 2 files changed, 15 insertions(+), 14 deletions(-)

diff --git a/src/com/esotericsoftware/kryo/util/ArrayClassResolver.java b/src/com/esotericsoftware/kryo/util/ArrayClassResolver.java
index 260698a90..db248b286 100644
--- a/src/com/esotericsoftware/kryo/util/ArrayClassResolver.java
+++ b/src/com/esotericsoftware/kryo/util/ArrayClassResolver.java
@@ -35,9 +35,9 @@
  * @apiNote In terms of functionality, {@link ArrayClassResolver} is completely equivalent to {@link DefaultClassResolver}. So
  *          output binary of {@link ArrayClassResolver} is equivalent to that of {@link DefaultClassResolver}.
  * @implNote You can specify the mappings between class and ID by {@link Kryo#register(Class, int)}, But don't specify huge ID
- *           like 20000000 because this resolver uses array internally. This resolver internally reconstructs
- *           {@link IntToObjArray#array} when the mappings are updated. Therefore, it is not suitable in terms of performance if
- *           the mappings are added frequently at peaktime of application. Use the {@link Pool}.
+ *           like 20000000 because this resolver uses array internally. This resolver internally reconstructs array of
+ *           {@link IntToObjArray} when the mappings are added. Therefore, it is not suitable in terms of performance if the
+ *           mappings are added frequently at peaktime of application. Use the {@link Pool}.
  * @see Pool */
 public class ArrayClassResolver implements ClassResolver {
 	protected final IdentityMap classToRegistration = new IdentityMap<>();
@@ -131,8 +131,8 @@ protected void writeName (Output output, Class type, Registration registration)
 
 	protected Registration readName (Input input) {
 		int nameId = input.readVarInt(true);
-		if (nameIdToClass == null) nameIdToClass = new IntToObjArray<>(Class.class, 20);
-		Class type = nameIdToClass.array[nameId];
+		if (nameIdToClass == null) nameIdToClass = new IntToObjArray<>(Class.class, nameId + 20);
+		Class type = nameIdToClass.get(nameId);
 		if (type == null) {
 			// Only read the class name the first time encountered in object graph.
 			String className = input.readString();
@@ -173,9 +173,7 @@ public void reset () {
 
 	@Override
 	public final Registration getRegistration (int classID) {
-		if (classID >= idToRegistrationTmp.array.length)
-			return null;
-		return idToRegistrationTmp.array[classID];
+		return idToRegistrationTmp.get(classID);
 	}
 
 	@Override
@@ -189,7 +187,6 @@ public Registration readClass (Input input) {
 			return readName(input);
 		}
 
-		// check cache
 		if (classID == memoizedClassId) {
 			if (TRACE) trace("kryo",
 				"Read class " + (classID - 2) + ": " + className(memoizedClassIdValue.getType()) + pos(input.position()));
@@ -201,7 +198,6 @@ public Registration readClass (Input input) {
 		if (registration == null) throw new KryoException("Encountered unregistered class ID: " + (classID - 2));
 		if (TRACE) trace("kryo", "Read class " + (classID - 2) + ": " + className(registration.getType()) + pos(input.position()));
 
-		// set cache
 		memoizedClassId = classID;
 		memoizedClassIdValue = registration;
 
diff --git a/src/com/esotericsoftware/kryo/util/IntToObjArray.java b/src/com/esotericsoftware/kryo/util/IntToObjArray.java
index 0b31924bd..f55ccdd34 100644
--- a/src/com/esotericsoftware/kryo/util/IntToObjArray.java
+++ b/src/com/esotericsoftware/kryo/util/IntToObjArray.java
@@ -24,15 +24,20 @@
 /** LUT by array.
  *
  * @apiNote don't specify large int to key by {@link #put(int, Object)}.
- * @author lifeinwild1@gmail.com
- * */
+ * @author lifeinwild1@gmail.com */
 public final class IntToObjArray {
 	private final Class valueType;
 	private final int initialCapacity;
 	private final float loadFactor;
-	public E[] array;
+	private E[] array;
+
+	public final E get (int key) {
+		if (key >= array.length || key < 0) {
+			return null;
+		}
+		return array[key];
+	}
 
-	/** @param valueType for generic type array {@link #array} */
 	IntToObjArray (Class valueType, int initialCapacity, float loadFactor) {
 		this.valueType = valueType;
 		array = (E[])Array.newInstance(valueType, initialCapacity);

From c593e884058798592208429c26d2cbff1eb574b7 Mon Sep 17 00:00:00 2001
From: lifeinwild 
Date: Sun, 30 Jan 2022 10:48:41 +0900
Subject: [PATCH 15/20] javadoc

---
 src/com/esotericsoftware/kryo/util/ArrayClassResolver.java | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/src/com/esotericsoftware/kryo/util/ArrayClassResolver.java b/src/com/esotericsoftware/kryo/util/ArrayClassResolver.java
index db248b286..57d0a2b40 100644
--- a/src/com/esotericsoftware/kryo/util/ArrayClassResolver.java
+++ b/src/com/esotericsoftware/kryo/util/ArrayClassResolver.java
@@ -32,8 +32,8 @@
 /** This is enhanced resolver from {@link DefaultClassResolver} for fast deserialization of collections.
  *
  * @author lifeinwild1@gmail.com
- * @apiNote In terms of functionality, {@link ArrayClassResolver} is completely equivalent to {@link DefaultClassResolver}. So
- *          output binary of {@link ArrayClassResolver} is equivalent to that of {@link DefaultClassResolver}.
+ * @apiNote In terms of functionality, {@link ArrayClassResolver} is completely equivalent to {@link DefaultClassResolver}. So the
+ *          output binary is equivalent to that of {@link DefaultClassResolver}.
  * @implNote You can specify the mappings between class and ID by {@link Kryo#register(Class, int)}, But don't specify huge ID
  *           like 20000000 because this resolver uses array internally. This resolver internally reconstructs array of
  *           {@link IntToObjArray} when the mappings are added. Therefore, it is not suitable in terms of performance if the

From 3b507b9e07bd6eb2dacdf3752367f7f827ff1ca7 Mon Sep 17 00:00:00 2001
From: lifeinwild 
Date: Sun, 30 Jan 2022 11:56:45 +0900
Subject: [PATCH 16/20] rename and exception in constructor

---
 .../esotericsoftware/kryo/util/IntToObjArray.java  | 14 +++++++++-----
 1 file changed, 9 insertions(+), 5 deletions(-)

diff --git a/src/com/esotericsoftware/kryo/util/IntToObjArray.java b/src/com/esotericsoftware/kryo/util/IntToObjArray.java
index f55ccdd34..08386a48b 100644
--- a/src/com/esotericsoftware/kryo/util/IntToObjArray.java
+++ b/src/com/esotericsoftware/kryo/util/IntToObjArray.java
@@ -28,7 +28,7 @@
 public final class IntToObjArray {
 	private final Class valueType;
 	private final int initialCapacity;
-	private final float loadFactor;
+	private final float expandRate;
 	private E[] array;
 
 	public final E get (int key) {
@@ -38,15 +38,19 @@ public final E get (int key) {
 		return array[key];
 	}
 
-	IntToObjArray (Class valueType, int initialCapacity, float loadFactor) {
+	IntToObjArray (Class valueType, int initialCapacity, float expandRate) {
+		if (expandRate <= 1.0)
+			throw new IllegalArgumentException("expandRate <= 1.0");
+		if (initialCapacity <= 0)
+			throw new IllegalArgumentException("initialCapacity <= 0");
 		this.valueType = valueType;
 		array = (E[])Array.newInstance(valueType, initialCapacity);
 		this.initialCapacity = initialCapacity;
-		this.loadFactor = loadFactor;
+		this.expandRate = expandRate;
 	}
 
 	IntToObjArray (Class valueType, int initialCapacity) {
-		this(valueType, initialCapacity, 0.75f);
+		this(valueType, initialCapacity, 1.1f);
 	}
 
 	IntToObjArray (Class valueType) {
@@ -67,7 +71,7 @@ public E remove (int classid) {
 
 	public E put (int classid, E v) {
 		if (classid >= array.length) {
-			E[] next = (E[])Array.newInstance(valueType, (int)(array.length * (1 + loadFactor)));
+			E[] next = (E[])Array.newInstance(valueType, (int)(array.length * expandRate));
 			System.arraycopy(array, 0, next, 0, array.length);
 			array = next;
 		}

From 80a278bb4b15c6c2b86bd63a15d2439efadb430e Mon Sep 17 00:00:00 2001
From: lifeinwild 
Date: Sun, 30 Jan 2022 12:03:41 +0900
Subject: [PATCH 17/20] rename and javadoc

---
 src/com/esotericsoftware/kryo/util/IntToObjArray.java     | 8 ++++----
 .../kryo/util/ArrayClassResolverTest.java                 | 2 +-
 2 files changed, 5 insertions(+), 5 deletions(-)

diff --git a/src/com/esotericsoftware/kryo/util/IntToObjArray.java b/src/com/esotericsoftware/kryo/util/IntToObjArray.java
index 08386a48b..cb1cc2985 100644
--- a/src/com/esotericsoftware/kryo/util/IntToObjArray.java
+++ b/src/com/esotericsoftware/kryo/util/IntToObjArray.java
@@ -38,15 +38,15 @@ public final E get (int key) {
 		return array[key];
 	}
 
-	IntToObjArray (Class valueType, int initialCapacity, float expandRate) {
-		if (expandRate <= 1.0)
-			throw new IllegalArgumentException("expandRate <= 1.0");
+	IntToObjArray (Class valueType, int initialCapacity, float expansionRate) {
+		if (expansionRate <= 1.0)
+			throw new IllegalArgumentException("expansionRate <= 1.0");
 		if (initialCapacity <= 0)
 			throw new IllegalArgumentException("initialCapacity <= 0");
 		this.valueType = valueType;
 		array = (E[])Array.newInstance(valueType, initialCapacity);
 		this.initialCapacity = initialCapacity;
-		this.expandRate = expandRate;
+		this.expandRate = expansionRate;
 	}
 
 	IntToObjArray (Class valueType, int initialCapacity) {
diff --git a/test/com/esotericsoftware/kryo/util/ArrayClassResolverTest.java b/test/com/esotericsoftware/kryo/util/ArrayClassResolverTest.java
index 1eab28cf0..1b150de50 100644
--- a/test/com/esotericsoftware/kryo/util/ArrayClassResolverTest.java
+++ b/test/com/esotericsoftware/kryo/util/ArrayClassResolverTest.java
@@ -30,7 +30,7 @@
 import static org.junit.jupiter.api.Assertions.*;
 
 /**
- * This class has only few test, but I tested {@link ArrayClassResolver} fully by below method:
+ * This class has only few test, but I fully tested {@link ArrayClassResolver} by below method:
  * I temporarily modified the kryo instance of {@link KryoTestCase#setUp()}, then all test cases passed.
  *
  * 

From 57ce07a9f1c96cb36db484f2a9ef7612e39ba88a Mon Sep 17 00:00:00 2001
From: lifeinwild 
Date: Sun, 13 Feb 2022 16:59:28 +0900
Subject: [PATCH 18/20] bug fix and new test case about huge ID

---
 .../kryo/util/IntToObjArray.java              |  3 +-
 .../kryo/util/ArrayClassResolverTest.java     | 84 +++++++++++++++++--
 2 files changed, 81 insertions(+), 6 deletions(-)

diff --git a/src/com/esotericsoftware/kryo/util/IntToObjArray.java b/src/com/esotericsoftware/kryo/util/IntToObjArray.java
index cb1cc2985..3697cbe4e 100644
--- a/src/com/esotericsoftware/kryo/util/IntToObjArray.java
+++ b/src/com/esotericsoftware/kryo/util/IntToObjArray.java
@@ -71,7 +71,8 @@ public E remove (int classid) {
 
 	public E put (int classid, E v) {
 		if (classid >= array.length) {
-			E[] next = (E[])Array.newInstance(valueType, (int)(array.length * expandRate));
+			int nextSize = (int)(classid * expandRate);
+			E[] next = (E[])Array.newInstance(valueType, nextSize);
 			System.arraycopy(array, 0, next, 0, array.length);
 			array = next;
 		}
diff --git a/test/com/esotericsoftware/kryo/util/ArrayClassResolverTest.java b/test/com/esotericsoftware/kryo/util/ArrayClassResolverTest.java
index 1b150de50..e10da35ee 100644
--- a/test/com/esotericsoftware/kryo/util/ArrayClassResolverTest.java
+++ b/test/com/esotericsoftware/kryo/util/ArrayClassResolverTest.java
@@ -21,6 +21,10 @@
 
 import com.esotericsoftware.kryo.Kryo;
 import com.esotericsoftware.kryo.KryoTestCase;
+import com.esotericsoftware.kryo.Registration;
+import com.esotericsoftware.kryo.Serializer;
+import com.esotericsoftware.kryo.io.Input;
+import com.esotericsoftware.kryo.io.Output;
 import org.junit.jupiter.api.BeforeEach;
 import org.junit.jupiter.api.Test;
 
@@ -30,8 +34,8 @@
 import static org.junit.jupiter.api.Assertions.*;
 
 /**
- * This class has only few test, but I fully tested {@link ArrayClassResolver} by below method:
- * I temporarily modified the kryo instance of {@link KryoTestCase#setUp()}, then all test cases passed.
+ * You can test {@link ArrayClassResolver} also by below method:
+ * temporarily modify {@link KryoTestCase#setUp()}, then run all test cases.
  *
  * 
  * @BeforeEach
@@ -40,8 +44,6 @@
  *
  * 	 kryo = new Kryo(new ArrayClassResolver(), null);
  * }
- *
- * Tests passed: 267 of 267 tests
  * 
* * @author lifeinwild1@gmail.com @@ -57,7 +59,79 @@ public void setUp () throws Exception { } @Test - void testBasic () { + void testHugeID () { + ArrayClassResolver resolver = new ArrayClassResolver(); + + int id0 = 0; + Registration input0 = new Registration(TestModel0.class, new TestSerializer0(), id0); + resolver.register(input0); + + int id1 = 1; + Registration input1 = new Registration(TestModel1.class, new TestSerializer1(), id1); + resolver.register(input1); + + int id1000000 = 1000000; + Registration input1000000 = new Registration(TestModel1000000.class, new TestSerializer1000000(), id1000000); + resolver.register(input1000000); + + Registration r0 = resolver.getRegistration(id0); + assertEquals(input0, r0); + + Registration r1 = resolver.getRegistration(id1); + assertEquals(input1, r1); + + Registration r1000000 = resolver.getRegistration(id1000000); + assertEquals(input1000000, r1000000); + } + + private static class TestModel1{ + + } + private static class TestSerializer1 extends Serializer { + + @Override + public void write(Kryo kryo, Output output, TestModel1 object) { + + } + + @Override + public TestModel1 read(Kryo kryo, Input input, Class type) { + return null; + } + } + + private static class TestModel0{ + + } + private static class TestSerializer0 extends Serializer { + + @Override + public void write(Kryo kryo, Output output, TestModel0 object) { + + } + + @Override + public TestModel0 read(Kryo kryo, Input input, Class type) { + return null; + } + } + private static class TestModel1000000{ + + } + private static class TestSerializer1000000 extends Serializer { + + @Override + public void write(Kryo kryo, Output output, TestModel1000000 object) { + + } + + @Override + public TestModel1000000 read(Kryo kryo, Input input, Class type) { + return null; + } + } + @Test + void testArrayList () { ArrayList test = new ArrayList(); test.add("one"); test.add("two"); From e7951fefbcaab0c14e872f79556b6a7738537f44 Mon Sep 17 00:00:00 2001 From: lifeinwild Date: Sun, 13 Feb 2022 17:22:12 +0900 Subject: [PATCH 19/20] performance improvement about Huge ID --- src/com/esotericsoftware/kryo/util/IntToObjArray.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/com/esotericsoftware/kryo/util/IntToObjArray.java b/src/com/esotericsoftware/kryo/util/IntToObjArray.java index 3697cbe4e..284ce52aa 100644 --- a/src/com/esotericsoftware/kryo/util/IntToObjArray.java +++ b/src/com/esotericsoftware/kryo/util/IntToObjArray.java @@ -70,7 +70,7 @@ public E remove (int classid) { } public E put (int classid, E v) { - if (classid >= array.length) { + if (classid >= array.length && array.length < Integer.MAX_VALUE) { int nextSize = (int)(classid * expandRate); E[] next = (E[])Array.newInstance(valueType, nextSize); System.arraycopy(array, 0, next, 0, array.length); From 913f1a695f144c5d39f6e49af517b548bfe30f0c Mon Sep 17 00:00:00 2001 From: lifeinwild Date: Sat, 19 Feb 2022 03:19:38 +0900 Subject: [PATCH 20/20] delete ad hoc code for profiler --- .../ArrayClassResolverBenchmark.java | 58 ++----------------- 1 file changed, 6 insertions(+), 52 deletions(-) diff --git a/benchmarks/src/main/java/com/esotericsoftware/kryo/benchmarks/ArrayClassResolverBenchmark.java b/benchmarks/src/main/java/com/esotericsoftware/kryo/benchmarks/ArrayClassResolverBenchmark.java index e0de25c3e..1165bcec0 100644 --- a/benchmarks/src/main/java/com/esotericsoftware/kryo/benchmarks/ArrayClassResolverBenchmark.java +++ b/benchmarks/src/main/java/com/esotericsoftware/kryo/benchmarks/ArrayClassResolverBenchmark.java @@ -33,10 +33,14 @@ * {@link ArrayClassResolver} is fast especially in {@link #deserializeCollection(DeserializingCollectionWithArrayClassResolverState)}}. * *
- * new module/old module=4125/3511=17% faster in a environment.
  * Benchmark                                           Mode  Cnt     Score   Error  Units
- * ArrayClassResolverBenchmark.deserializeCollection  thrpt       4125.996          ops/s
+ * ArrayClassResolverBenchmark.deserializeCollection  thrpt       4125.996          ops/s +17%
  * FieldSerializerBenchmark.deserializeCollection     thrpt       3511.014          ops/s
+ *
+ * Comparing only readClass() by profiler.
+ * 									Total Time	Invocations
+ * DefaultClassResolver.readClass()	19,872 ms 	10,006,631
+ * ArrayClassResolver.readClass()	12,371 ms 	10,029,051		60% faster
  * 
* * @author lifeinwild1@gmail.com @@ -76,56 +80,6 @@ public static Kryo createKryoArray(){ return new Kryo(new ArrayClassResolver(), null); } - /** - * ad-hoc code for profiling - */ - public static void main(String args[]){ - //ArrayClassResolver is efficient for collection. - Map m = new HashMap<>(); - - //The performance difference is proportional to the size of collection. - int size = 2000; - for(long i=0;i deserialized = (HashMap)k.readClassAndObject(in); - if(!m.equals(deserialized)){ - System.out.println("error"); - } - } - - /* - Result: - Total Time Invocations - DefaultClassResolver.readClass() 19,872 ms 10,006,631 - ArrayClassResolver.readClass() 12,371 ms 10,029,051 60% faster - */ - } - static public class FieldSerializerStateArray extends FieldSerializerState { @Override public Kryo createKryo() {