8196207: Inefficient ArrayList.subList().toArray()

Reviewed-by: martin, psandoz, jrose, redestad
This commit is contained in:
Sergei Tsypanov 2018-01-30 11:08:50 -08:00 committed by Doug Lea
parent 35a7cc10b7
commit bbb4bcd69d
3 changed files with 124 additions and 34 deletions

View file

@ -1143,6 +1143,23 @@ public class ArrayList<E> extends AbstractList<E>
return modified; 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() { public Iterator<E> iterator() {
return listIterator(); return listIterator();
} }

View file

@ -642,6 +642,24 @@ public class IteratorMicroBenchmark {
for (Object o : a) for (Object o : a)
sum[0] += (Integer) o; sum[0] += (Integer) o;
check.sum(sum[0]);}}}, 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()") { new Job("ArrayDeque.toArray()") {
public void work() throws Throwable { public void work() throws Throwable {
int[] sum = new int[1]; int[] sum = new int[1];

View file

@ -28,7 +28,7 @@
*/ */
import static java.util.stream.Collectors.summingInt; 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.lang.ref.WeakReference;
import java.util.ArrayDeque; import java.util.ArrayDeque;
@ -46,6 +46,7 @@ import java.util.Vector;
import java.util.concurrent.ArrayBlockingQueue; import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.ConcurrentLinkedDeque; import java.util.concurrent.ConcurrentLinkedDeque;
import java.util.concurrent.ConcurrentLinkedQueue; import java.util.concurrent.ConcurrentLinkedQueue;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.LinkedBlockingDeque; import java.util.concurrent.LinkedBlockingDeque;
import java.util.concurrent.LinkedBlockingQueue; import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.LinkedTransferQueue; import java.util.concurrent.LinkedTransferQueue;
@ -55,6 +56,7 @@ import java.util.concurrent.ThreadLocalRandom;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.LongAdder; import java.util.concurrent.atomic.LongAdder;
import java.util.regex.Pattern; import java.util.regex.Pattern;
import java.util.stream.Stream;
/** /**
* Usage: [iterations=N] [size=N] [filter=REGEXP] [warmup=SECONDS] * Usage: [iterations=N] [size=N] [filter=REGEXP] [warmup=SECONDS]
@ -79,7 +81,7 @@ public class IteratorMicroBenchmark {
final int size; // number of elements in collections final int size; // number of elements in collections
final double warmupSeconds; final double warmupSeconds;
final long warmupNanos; 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 reverse; // reverse order of Jobs
final boolean shuffle; // randomize order of Jobs final boolean shuffle; // randomize order of Jobs
@ -87,7 +89,7 @@ public class IteratorMicroBenchmark {
iterations = intArg(args, "iterations", 10_000); iterations = intArg(args, "iterations", 10_000);
size = intArg(args, "size", 1000); size = intArg(args, "size", 1000);
warmupSeconds = doubleArg(args, "warmup", 7.0); warmupSeconds = doubleArg(args, "warmup", 7.0);
filter = patternArg(args, "filter"); nameFilter = patternArg(args, "filter");
reverse = booleanArg(args, "reverse"); reverse = booleanArg(args, "reverse");
shuffle = booleanArg(args, "shuffle"); shuffle = booleanArg(args, "shuffle");
@ -204,13 +206,6 @@ public class IteratorMicroBenchmark {
throw new IllegalArgumentException(val); 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) { private static void deoptimize(int sum) {
if (sum == 42) if (sum == 42)
System.out.println("the answer"); System.out.println("the answer");
@ -249,7 +244,7 @@ public class IteratorMicroBenchmark {
void run() throws Throwable { void run() throws Throwable {
// System.out.printf( // System.out.printf(
// "iterations=%d size=%d, warmup=%1g, filter=\"%s\"%n", // "iterations=%d size=%d, warmup=%1g, filter=\"%s\"%n",
// iterations, size, warmupSeconds, filter); // iterations, size, warmupSeconds, nameFilter);
final ArrayList<Integer> al = new ArrayList<>(size); final ArrayList<Integer> al = new ArrayList<>(size);
@ -268,34 +263,48 @@ public class IteratorMicroBenchmark {
abq.add(abq.remove()); abq.add(abq.remove());
} }
ArrayList<Job> jobs = new ArrayList<>(); ArrayList<Job> jobs = Stream.<Collection<Integer>>of(
List.<Collection<Integer>>of(
al, ad, abq, al, ad, abq,
new LinkedList<>(al), new LinkedList<>(al),
new PriorityQueue<>(al), new PriorityQueue<>(al),
new Vector<>(al), new Vector<>(al),
new CopyOnWriteArrayList<>(al),
new ConcurrentLinkedQueue<>(al), new ConcurrentLinkedQueue<>(al),
new ConcurrentLinkedDeque<>(al), new ConcurrentLinkedDeque<>(al),
new LinkedBlockingQueue<>(al), new LinkedBlockingQueue<>(al),
new LinkedBlockingDeque<>(al), new LinkedBlockingDeque<>(al),
new LinkedTransferQueue<>(al), new LinkedTransferQueue<>(al),
new PriorityBlockingQueue<>(al)).forEach( new PriorityBlockingQueue<>(al))
x -> { .flatMap(x -> jobs(x))
jobs.addAll(collectionJobs(x)); .filter(job ->
if (x instanceof Deque) nameFilter == null || nameFilter.matcher(job.name()).find())
jobs.addAll(dequeJobs((Deque<Integer>)x)); .collect(toCollection(ArrayList::new));
});
if (reverse) Collections.reverse(jobs); if (reverse) Collections.reverse(jobs);
if (shuffle) Collections.shuffle(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(); String klazz = x.getClass().getSimpleName();
return List.of( return Stream.of(
new Job(klazz + " iterate for loop") { new Job(klazz + " iterate for loop") {
public void work() throws Throwable { public void work() throws Throwable {
for (int i = 0; i < iterations; i++) { for (int i = 0; i < iterations; i++) {
@ -436,9 +445,9 @@ public class IteratorMicroBenchmark {
check.sum(sum[0]);}}}); check.sum(sum[0]);}}});
} }
List<Job> dequeJobs(Deque<Integer> x) { Stream<Job> dequeJobs(Deque<Integer> x) {
String klazz = x.getClass().getSimpleName(); String klazz = x.getClass().getSimpleName();
return List.of( return Stream.of(
new Job(klazz + " descendingIterator() loop") { new Job(klazz + " descendingIterator() loop") {
public void work() throws Throwable { public void work() throws Throwable {
for (int i = 0; i < iterations; i++) { for (int i = 0; i < iterations; i++) {
@ -455,4 +464,50 @@ public class IteratorMicroBenchmark {
x.descendingIterator().forEachRemaining(n -> sum[0] += n); x.descendingIterator().forEachRemaining(n -> sum[0] += n);
check.sum(sum[0]);}}}); 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);}}});
}
} }