From a5d948fb9841f654cccc9567c60e8d28e7d719ae Mon Sep 17 00:00:00 2001 From: John Engebretson Date: Wed, 14 Aug 2024 14:11:53 +0000 Subject: [PATCH] 8332842: Optimize empty CopyOnWriteArrayList allocations Reviewed-by: shade, alanb --- .../util/concurrent/CopyOnWriteArrayList.java | 22 ++- .../CopyOnWriteArrayListBenchmark.java | 166 ++++++++++++++++++ 2 files changed, 184 insertions(+), 4 deletions(-) create mode 100644 test/micro/org/openjdk/bench/java/util/concurrent/CopyOnWriteArrayListBenchmark.java diff --git a/src/java.base/share/classes/java/util/concurrent/CopyOnWriteArrayList.java b/src/java.base/share/classes/java/util/concurrent/CopyOnWriteArrayList.java index 7182f98706d..f0f60730eb6 100644 --- a/src/java.base/share/classes/java/util/concurrent/CopyOnWriteArrayList.java +++ b/src/java.base/share/classes/java/util/concurrent/CopyOnWriteArrayList.java @@ -100,6 +100,8 @@ public class CopyOnWriteArrayList implements List, RandomAccess, Cloneable, java.io.Serializable { private static final long serialVersionUID = 8673264195747942595L; + private static final Object[] EMPTY_ELEMENTDATA = {}; + /** * The lock protecting all mutators. (We have a mild preference * for builtin monitors over ReentrantLock when either will do.) @@ -128,7 +130,7 @@ public class CopyOnWriteArrayList * Creates an empty list. */ public CopyOnWriteArrayList() { - setArray(new Object[0]); + setArray(EMPTY_ELEMENTDATA); } /** @@ -143,6 +145,8 @@ public class CopyOnWriteArrayList Object[] es; if (c.getClass() == CopyOnWriteArrayList.class) es = ((CopyOnWriteArrayList)c).getArray(); + else if (c.isEmpty()) + es = EMPTY_ELEMENTDATA; else { es = c.toArray(); if (c.getClass() != java.util.ArrayList.class) @@ -159,7 +163,10 @@ public class CopyOnWriteArrayList * @throws NullPointerException if the specified array is null */ public CopyOnWriteArrayList(E[] toCopyIn) { - setArray(Arrays.copyOf(toCopyIn, toCopyIn.length, Object[].class)); + if (toCopyIn.length == 0) + setArray(EMPTY_ELEMENTDATA); + else + setArray(Arrays.copyOf(toCopyIn, toCopyIn.length, Object[].class)); } /** @@ -533,6 +540,8 @@ public class CopyOnWriteArrayList Object[] newElements; if (numMoved == 0) newElements = Arrays.copyOf(es, len - 1); + else if (len == 1) + newElements = EMPTY_ELEMENTDATA; else { newElements = new Object[len - 1]; System.arraycopy(es, 0, newElements, 0, index); @@ -618,6 +627,11 @@ public class CopyOnWriteArrayList if (index < 0) return false; } + if (len == 1) { + // one element exists and that element should be removed + setArray(EMPTY_ELEMENTDATA); + return true; + } Object[] newElements = new Object[len - 1]; System.arraycopy(current, 0, newElements, 0, index); System.arraycopy(current, index + 1, @@ -804,7 +818,7 @@ public class CopyOnWriteArrayList */ public void clear() { synchronized (lock) { - setArray(new Object[0]); + setArray(EMPTY_ELEMENTDATA); } } @@ -1022,7 +1036,7 @@ public class CopyOnWriteArrayList // Read in array length and allocate array int len = s.readInt(); SharedSecrets.getJavaObjectInputStreamAccess().checkArray(s, Object[].class, len); - Object[] es = new Object[len]; + Object[] es = (len == 0 ? EMPTY_ELEMENTDATA : new Object[len]); // Read in all elements in the proper order. for (int i = 0; i < len; i++) diff --git a/test/micro/org/openjdk/bench/java/util/concurrent/CopyOnWriteArrayListBenchmark.java b/test/micro/org/openjdk/bench/java/util/concurrent/CopyOnWriteArrayListBenchmark.java new file mode 100644 index 00000000000..0ce091d1724 --- /dev/null +++ b/test/micro/org/openjdk/bench/java/util/concurrent/CopyOnWriteArrayListBenchmark.java @@ -0,0 +1,166 @@ +/* + * Copyright Amazon.com Inc. 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. + */ +package org.openjdk.bench.java.util.concurrent; + +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.ObjectInputStream; +import java.io.ObjectOutputStream; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.concurrent.CopyOnWriteArrayList; +import java.util.concurrent.TimeUnit; + +import org.openjdk.jmh.annotations.Benchmark; +import org.openjdk.jmh.annotations.BenchmarkMode; +import org.openjdk.jmh.annotations.Fork; +import org.openjdk.jmh.annotations.Measurement; +import org.openjdk.jmh.annotations.Mode; +import org.openjdk.jmh.annotations.OutputTimeUnit; +import org.openjdk.jmh.annotations.Scope; +import org.openjdk.jmh.annotations.State; +import org.openjdk.jmh.annotations.Warmup; + +@BenchmarkMode(Mode.AverageTime) +@State(Scope.Benchmark) +@OutputTimeUnit(TimeUnit.NANOSECONDS) +@Fork(1) +@Warmup(iterations = 3, time = 1, timeUnit = TimeUnit.SECONDS) +@Measurement(iterations = 5, time = 1, timeUnit = TimeUnit.SECONDS) +public class CopyOnWriteArrayListBenchmark { + + private static byte[] getSerializedBytes(CopyOnWriteArrayList list) throws IOException { + ByteArrayOutputStream bytesOut = new ByteArrayOutputStream(); + ObjectOutputStream objectOut = new ObjectOutputStream(bytesOut); + objectOut.writeObject(list); + + objectOut.close(); + return bytesOut.toByteArray(); + } + + private Collection emptyCollection = new ArrayList<>(); + private Object[] emptyArray = new Object[0]; + + private Collection oneItemCollection = Arrays.asList(""); + private Object[] oneItemArray = new Object[] { "" }; + + private CopyOnWriteArrayList emptyInstance = new CopyOnWriteArrayList<>(); + private CopyOnWriteArrayList oneItemInstance = new CopyOnWriteArrayList<>(oneItemArray); + + private byte[] emptyInstanceBytes; + private byte[] oneInstanceBytes; + + public CopyOnWriteArrayListBenchmark() { + try { + emptyInstanceBytes = getSerializedBytes(emptyInstance); + oneInstanceBytes = getSerializedBytes(oneItemInstance); + } catch (IOException e) { + throw new RuntimeException(e); + } + } + + @Benchmark + public void clear() { + // have to create a new instance on each execution + ((CopyOnWriteArrayList) oneItemInstance.clone()).clear(); + } + + @Benchmark + public void clearEmpty() { + emptyInstance.clear(); + } + + @Benchmark + public CopyOnWriteArrayList createInstanceArray() { + return new CopyOnWriteArrayList<>(oneItemArray); + } + + @Benchmark + public CopyOnWriteArrayList createInstanceArrayEmpty() { + return new CopyOnWriteArrayList<>(emptyArray); + } + + @Benchmark + public CopyOnWriteArrayList createInstanceCollection() { + return new CopyOnWriteArrayList<>(oneItemCollection); + } + + @Benchmark + public CopyOnWriteArrayList createInstanceCollectionEmpty() { + return new CopyOnWriteArrayList<>(emptyCollection); + } + + @Benchmark + public CopyOnWriteArrayList createInstanceDefault() { + return new CopyOnWriteArrayList(); + } + + @Benchmark + public CopyOnWriteArrayList readInstance() throws IOException, ClassNotFoundException { + try (ObjectInputStream objIn = new ObjectInputStream(new ByteArrayInputStream(oneInstanceBytes))) { + return (CopyOnWriteArrayList) objIn.readObject(); + } + } + + @Benchmark + public CopyOnWriteArrayList readInstanceEmpty() throws IOException, ClassNotFoundException { + try (ObjectInputStream objIn = new ObjectInputStream(new ByteArrayInputStream(emptyInstanceBytes))) { + return (CopyOnWriteArrayList) objIn.readObject(); + } + } + + @Benchmark + public CopyOnWriteArrayList removeObjectLastRemaining() { + CopyOnWriteArrayList list = new CopyOnWriteArrayList<>(); + list.add(""); + list.remove(""); + return list; + } + + @Benchmark + public CopyOnWriteArrayList removeIndexLastRemaining() { + CopyOnWriteArrayList list = new CopyOnWriteArrayList<>(); + list.add(""); + list.remove(0); + return list; + } + @Benchmark + public CopyOnWriteArrayList removeObject() { + CopyOnWriteArrayList list = new CopyOnWriteArrayList<>(); + list.add(""); + list.add("a"); + list.remove(""); + return list; + } + + @Benchmark + public CopyOnWriteArrayList remove() { + CopyOnWriteArrayList list = new CopyOnWriteArrayList<>(); + list.add(""); + list.add("a"); + list.remove(0); + return list; + } +}