# HG changeset patch # User dl # Date 1517339330 28800 # Node ID e3dcdd73a549cd58dd553a185e5519b8f0ffc027 # Parent f13f9b941bc78cd396c8b6a4b7dca277c38ebb8e 8196207: Inefficient ArrayList.subList().toArray() Reviewed-by: martin, psandoz, jrose, redestad Contributed-by: Sergei Tsypanov diff -r f13f9b941bc7 -r e3dcdd73a549 src/java.base/share/classes/java/util/ArrayList.java --- a/src/java.base/share/classes/java/util/ArrayList.java Tue Jan 30 10:42:13 2018 -0800 +++ b/src/java.base/share/classes/java/util/ArrayList.java Tue Jan 30 11:08:50 2018 -0800 @@ -1143,6 +1143,23 @@ return modified; } + public Object[] toArray() { + checkForComodification(); + return Arrays.copyOfRange(root.elementData, offset, offset + size); + } + + @SuppressWarnings("unchecked") + public T[] toArray(T[] a) { + checkForComodification(); + if (a.length < size) + return (T[]) Arrays.copyOfRange( + root.elementData, offset, offset + size, a.getClass()); + System.arraycopy(root.elementData, offset, a, 0, size); + if (a.length > size) + a[size] = null; + return a; + } + public Iterator iterator() { return listIterator(); } diff -r f13f9b941bc7 -r e3dcdd73a549 test/jdk/java/util/ArrayList/IteratorMicroBenchmark.java --- a/test/jdk/java/util/ArrayList/IteratorMicroBenchmark.java Tue Jan 30 10:42:13 2018 -0800 +++ b/test/jdk/java/util/ArrayList/IteratorMicroBenchmark.java Tue Jan 30 11:08:50 2018 -0800 @@ -642,6 +642,24 @@ for (Object o : a) sum[0] += (Integer) o; check.sum(sum[0]);}}}, + new Job("ArrayList subList .toArray()") { + public void work() throws Throwable { + int[] sum = new int[1]; + for (int i = 0; i < iterations; i++) { + sum[0] = 0; + for (Object o : asSubList(al).toArray()) + sum[0] += (Integer) o; + check.sum(sum[0]);}}}, + new Job("ArrayList subList .toArray(a)") { + public void work() throws Throwable { + Integer[] a = new Integer[size]; + int[] sum = new int[1]; + for (int i = 0; i < iterations; i++) { + sum[0] = 0; + asSubList(al).toArray(a); + for (Object o : a) + sum[0] += (Integer) o; + check.sum(sum[0]);}}}, new Job("ArrayDeque.toArray()") { public void work() throws Throwable { int[] sum = new int[1]; diff -r f13f9b941bc7 -r e3dcdd73a549 test/jdk/java/util/Collection/IteratorMicroBenchmark.java --- a/test/jdk/java/util/Collection/IteratorMicroBenchmark.java Tue Jan 30 10:42:13 2018 -0800 +++ b/test/jdk/java/util/Collection/IteratorMicroBenchmark.java Tue Jan 30 11:08:50 2018 -0800 @@ -28,7 +28,7 @@ */ import static java.util.stream.Collectors.summingInt; -import static java.util.stream.Collectors.toList; +import static java.util.stream.Collectors.toCollection; import java.lang.ref.WeakReference; import java.util.ArrayDeque; @@ -46,6 +46,7 @@ import java.util.concurrent.ArrayBlockingQueue; import java.util.concurrent.ConcurrentLinkedDeque; import java.util.concurrent.ConcurrentLinkedQueue; +import java.util.concurrent.CopyOnWriteArrayList; import java.util.concurrent.LinkedBlockingDeque; import java.util.concurrent.LinkedBlockingQueue; import java.util.concurrent.LinkedTransferQueue; @@ -55,6 +56,7 @@ import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.LongAdder; import java.util.regex.Pattern; +import java.util.stream.Stream; /** * Usage: [iterations=N] [size=N] [filter=REGEXP] [warmup=SECONDS] @@ -79,7 +81,7 @@ final int size; // number of elements in collections final double warmupSeconds; final long warmupNanos; - final Pattern filter; // select subset of Jobs to run + final Pattern nameFilter; // select subset of Jobs to run final boolean reverse; // reverse order of Jobs final boolean shuffle; // randomize order of Jobs @@ -87,7 +89,7 @@ iterations = intArg(args, "iterations", 10_000); size = intArg(args, "size", 1000); warmupSeconds = doubleArg(args, "warmup", 7.0); - filter = patternArg(args, "filter"); + nameFilter = patternArg(args, "filter"); reverse = booleanArg(args, "reverse"); shuffle = booleanArg(args, "shuffle"); @@ -204,13 +206,6 @@ throw new IllegalArgumentException(val); } - private static List filter(Pattern filter, List jobs) { - return (filter == null) ? jobs - : jobs.stream() - .filter(job -> filter.matcher(job.name()).find()) - .collect(toList()); - } - private static void deoptimize(int sum) { if (sum == 42) System.out.println("the answer"); @@ -249,7 +244,7 @@ void run() throws Throwable { // System.out.printf( // "iterations=%d size=%d, warmup=%1g, filter=\"%s\"%n", -// iterations, size, warmupSeconds, filter); +// iterations, size, warmupSeconds, nameFilter); final ArrayList al = new ArrayList<>(size); @@ -268,34 +263,48 @@ abq.add(abq.remove()); } - ArrayList jobs = new ArrayList<>(); - - List.>of( - al, ad, abq, - new LinkedList<>(al), - new PriorityQueue<>(al), - new Vector<>(al), - new ConcurrentLinkedQueue<>(al), - new ConcurrentLinkedDeque<>(al), - new LinkedBlockingQueue<>(al), - new LinkedBlockingDeque<>(al), - new LinkedTransferQueue<>(al), - new PriorityBlockingQueue<>(al)).forEach( - x -> { - jobs.addAll(collectionJobs(x)); - if (x instanceof Deque) - jobs.addAll(dequeJobs((Deque)x)); - }); + ArrayList jobs = Stream.>of( + al, ad, abq, + new LinkedList<>(al), + new PriorityQueue<>(al), + new Vector<>(al), + new CopyOnWriteArrayList<>(al), + new ConcurrentLinkedQueue<>(al), + new ConcurrentLinkedDeque<>(al), + new LinkedBlockingQueue<>(al), + new LinkedBlockingDeque<>(al), + new LinkedTransferQueue<>(al), + new PriorityBlockingQueue<>(al)) + .flatMap(x -> jobs(x)) + .filter(job -> + nameFilter == null || nameFilter.matcher(job.name()).find()) + .collect(toCollection(ArrayList::new)); if (reverse) Collections.reverse(jobs); if (shuffle) Collections.shuffle(jobs); - time(filter(filter, jobs)); + time(jobs); + } + + @SafeVarargs @SuppressWarnings("varargs") + private Stream concatStreams(Stream ... streams) { + return Stream.of(streams).flatMap(s -> s); } - List collectionJobs(Collection x) { + Stream jobs(Collection x) { + return concatStreams( + collectionJobs(x), + (x instanceof Deque) + ? dequeJobs((Deque)x) + : Stream.empty(), + (x instanceof List) + ? listJobs((List)x) + : Stream.empty()); + } + + Stream collectionJobs(Collection x) { String klazz = x.getClass().getSimpleName(); - return List.of( + return Stream.of( new Job(klazz + " iterate for loop") { public void work() throws Throwable { for (int i = 0; i < iterations; i++) { @@ -436,9 +445,9 @@ check.sum(sum[0]);}}}); } - List dequeJobs(Deque x) { + Stream dequeJobs(Deque x) { String klazz = x.getClass().getSimpleName(); - return List.of( + return Stream.of( new Job(klazz + " descendingIterator() loop") { public void work() throws Throwable { for (int i = 0; i < iterations; i++) { @@ -455,4 +464,50 @@ x.descendingIterator().forEachRemaining(n -> sum[0] += n); check.sum(sum[0]);}}}); } + + Stream listJobs(List x) { + String klazz = x.getClass().getSimpleName(); + return Stream.of( + new Job(klazz + " subList toArray()") { + public void work() throws Throwable { + int size = x.size(); + for (int i = 0; i < iterations; i++) { + int total = Stream.of(x.subList(0, size / 2), + x.subList(size / 2, size)) + .mapToInt(subList -> { + int sum = 0; + for (Object o : subList.toArray()) + sum += (Integer) o; + return sum; }) + .sum(); + check.sum(total);}}}, + new Job(klazz + " subList toArray(a)") { + public void work() throws Throwable { + int size = x.size(); + for (int i = 0; i < iterations; i++) { + int total = Stream.of(x.subList(0, size / 2), + x.subList(size / 2, size)) + .mapToInt(subList -> { + int sum = 0; + Integer[] a = new Integer[subList.size()]; + for (Object o : subList.toArray(a)) + sum += (Integer) o; + return sum; }) + .sum(); + check.sum(total);}}}, + new Job(klazz + " subList toArray(empty)") { + public void work() throws Throwable { + int size = x.size(); + Integer[] empty = new Integer[0]; + for (int i = 0; i < iterations; i++) { + int total = Stream.of(x.subList(0, size / 2), + x.subList(size / 2, size)) + .mapToInt(subList -> { + int sum = 0; + for (Object o : subList.toArray(empty)) + sum += (Integer) o; + return sum; }) + .sum(); + check.sum(total);}}}); + } }