changeset 48686:e3dcdd73a549

8196207: Inefficient ArrayList.subList().toArray() Reviewed-by: martin, psandoz, jrose, redestad Contributed-by: Sergei Tsypanov <sergei.tsypanov@yandex.ru>
author dl
date Tue, 30 Jan 2018 11:08:50 -0800
parents f13f9b941bc7
children d51e64840b4f
files src/java.base/share/classes/java/util/ArrayList.java test/jdk/java/util/ArrayList/IteratorMicroBenchmark.java test/jdk/java/util/Collection/IteratorMicroBenchmark.java
diffstat 3 files changed, 124 insertions(+), 34 deletions(-) [+]
line wrap: on
line diff
--- 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> 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<E> iterator() {
             return listIterator();
         }
--- 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];
--- 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<Job> filter(Pattern filter, List<Job> 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<Integer> al = new ArrayList<>(size);
 
@@ -268,34 +263,48 @@
             abq.add(abq.remove());
         }
 
-        ArrayList<Job> jobs = new ArrayList<>();
-
-        List.<Collection<Integer>>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<Integer>)x));
-                });
+        ArrayList<Job> jobs = Stream.<Collection<Integer>>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);
     }
 
-    List<Job> collectionJobs(Collection<Integer> x) {
+    @SafeVarargs @SuppressWarnings("varargs")
+    private <T> Stream<T> concatStreams(Stream<T> ... streams) {
+        return Stream.of(streams).flatMap(s -> s);
+    }
+
+    Stream<Job> jobs(Collection<Integer> x) {
+        return concatStreams(
+            collectionJobs(x),
+            (x instanceof Deque)
+            ? dequeJobs((Deque<Integer>)x)
+            : Stream.empty(),
+            (x instanceof List)
+            ? listJobs((List<Integer>)x)
+            : Stream.empty());
+    }
+
+    Stream<Job> collectionJobs(Collection<Integer> 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<Job> dequeJobs(Deque<Integer> x) {
+    Stream<Job> dequeJobs(Deque<Integer> 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<Job> listJobs(List<Integer> 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);}}});
+    }
 }