1 /*
   2  * Copyright (c) 2007, Oracle and/or its affiliates. All rights reserved.
   3  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
   4  *
   5  * This code is free software; you can redistribute it and/or modify it
   6  * under the terms of the GNU General Public License version 2 only, as
   7  * published by the Free Software Foundation.
   8  *
   9  * This code is distributed in the hope that it will be useful, but WITHOUT
  10  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
  11  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
  12  * version 2 for more details (a copy is included in the LICENSE file that
  13  * accompanied this code).
  14  *
  15  * You should have received a copy of the GNU General Public License version
  16  * 2 along with this work; if not, write to the Free Software Foundation,
  17  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
  18  *
  19  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
  20  * or visit www.oracle.com if you need additional information or have any
  21  * questions.
  22  */
  23 
  24 /*
  25  * @test
  26  * @summary micro-benchmark correctness mode
  27  * @run main IteratorMicroBenchmark iterations=1 size=8 warmup=0
  28  */
  29 
  30 import static java.util.concurrent.TimeUnit.MILLISECONDS;
  31 import static java.util.stream.Collectors.summingInt;
  32 import static java.util.stream.Collectors.toCollection;
  33 
  34 import java.lang.ref.ReferenceQueue;
  35 import java.lang.ref.WeakReference;
  36 import java.util.ArrayDeque;
  37 import java.util.Arrays;
  38 import java.util.ArrayList;
  39 import java.util.Collection;
  40 import java.util.Collections;
  41 import java.util.Deque;
  42 import java.util.HashMap;
  43 import java.util.Iterator;
  44 import java.util.LinkedList;
  45 import java.util.List;
  46 import java.util.ListIterator;
  47 import java.util.PriorityQueue;
  48 import java.util.Spliterator;
  49 import java.util.Vector;
  50 import java.util.concurrent.ArrayBlockingQueue;
  51 import java.util.concurrent.ConcurrentLinkedDeque;
  52 import java.util.concurrent.ConcurrentLinkedQueue;
  53 import java.util.concurrent.CopyOnWriteArrayList;
  54 import java.util.concurrent.LinkedBlockingDeque;
  55 import java.util.concurrent.LinkedBlockingQueue;
  56 import java.util.concurrent.LinkedTransferQueue;
  57 import java.util.concurrent.PriorityBlockingQueue;
  58 import java.util.concurrent.CountDownLatch;
  59 import java.util.concurrent.ThreadLocalRandom;
  60 import java.util.concurrent.atomic.LongAdder;
  61 import java.util.function.UnaryOperator;
  62 import java.util.regex.Pattern;
  63 import java.util.stream.Stream;
  64 
  65 /**
  66  * Usage: [iterations=N] [size=N] [filter=REGEXP] [warmup=SECONDS]
  67  *
  68  * To run this in micro-benchmark mode, simply run as a normal java program.
  69  * Be patient; this program runs for a very long time.
  70  * For faster runs, restrict execution using command line args.
  71  *
  72  * @author Martin Buchholz
  73  */
  74 public class IteratorMicroBenchmark {
  75     abstract static class Job {
  76         private final String name;
  77         public Job(String name) { this.name = name; }
  78         public String name() { return name; }
  79         public abstract void work() throws Throwable;
  80         public void run() {
  81             try { work(); }
  82             catch (Throwable ex) {
  83                 // current job cannot always be deduced from stacktrace.
  84                 throw new RuntimeException("Job failed: " + name(), ex);
  85             }
  86         }
  87     }
  88 
  89     final int iterations;
  90     final int size;             // number of elements in collections
  91     final double warmupSeconds;
  92     final long warmupNanos;
  93     final Pattern nameFilter;   // select subset of Jobs to run
  94     final boolean reverse;      // reverse order of Jobs
  95     final boolean shuffle;      // randomize order of Jobs
  96 
  97     IteratorMicroBenchmark(String[] args) {
  98         iterations    = intArg(args, "iterations", 10_000);
  99         size          = intArg(args, "size", 1000);
 100         warmupSeconds = doubleArg(args, "warmup", 7.0);
 101         nameFilter    = patternArg(args, "filter");
 102         reverse       = booleanArg(args, "reverse");
 103         shuffle       = booleanArg(args, "shuffle");
 104 
 105         warmupNanos = (long) (warmupSeconds * (1000L * 1000L * 1000L));
 106     }
 107 
 108     // --------------- GC finalization infrastructure ---------------
 109 
 110     /** No guarantees, but effective in practice. */
 111     static void forceFullGc() {
 112         long timeoutMillis = 1000L;
 113         CountDownLatch finalized = new CountDownLatch(1);
 114         ReferenceQueue<Object> queue = new ReferenceQueue<>();
 115         WeakReference<Object> ref = new WeakReference<>(
 116             new Object() {
 117                 @SuppressWarnings("deprecation")
 118                 protected void finalize() { finalized.countDown(); }},
 119             queue);
 120         try {
 121             for (int tries = 3; tries--> 0; ) {
 122                 System.gc();
 123                 if (finalized.await(timeoutMillis, MILLISECONDS)
 124                     && queue.remove(timeoutMillis) != null
 125                     && ref.get() == null) {
 126                     System.runFinalization(); // try to pick up stragglers
 127                     return;
 128                 }
 129                 timeoutMillis *= 4;
 130             }
 131         } catch (InterruptedException unexpected) {
 132             throw new AssertionError("unexpected InterruptedException");
 133         }
 134         throw new AssertionError("failed to do a \"full\" gc");
 135     }
 136 
 137     /**
 138      * Runs each job for long enough that all the runtime compilers
 139      * have had plenty of time to warm up, i.e. get around to
 140      * compiling everything worth compiling.
 141      * Returns array of average times per job per run.
 142      */
 143     long[] time0(List<Job> jobs) {
 144         final int size = jobs.size();
 145         long[] nanoss = new long[size];
 146         for (int i = 0; i < size; i++) {
 147             if (warmupNanos > 0) forceFullGc();
 148             Job job = jobs.get(i);
 149             long totalTime;
 150             int runs = 0;
 151             long startTime = System.nanoTime();
 152             do { job.run(); runs++; }
 153             while ((totalTime = System.nanoTime() - startTime) < warmupNanos);
 154             nanoss[i] = totalTime/runs;
 155         }
 156         return nanoss;
 157     }
 158 
 159     void time(List<Job> jobs) throws Throwable {
 160         if (warmupNanos > 0) time0(jobs); // Warm up run
 161         final int size = jobs.size();
 162         final long[] nanoss = time0(jobs); // Real timing run
 163         final long[] milliss = new long[size];
 164         final double[] ratios = new double[size];
 165 
 166         final String nameHeader   = "Method";
 167         final String millisHeader = "Millis";
 168         final String ratioHeader  = "Ratio";
 169 
 170         int nameWidth   = nameHeader.length();
 171         int millisWidth = millisHeader.length();
 172         int ratioWidth  = ratioHeader.length();
 173 
 174         for (int i = 0; i < size; i++) {
 175             nameWidth = Math.max(nameWidth, jobs.get(i).name().length());
 176 
 177             milliss[i] = nanoss[i]/(1000L * 1000L);
 178             millisWidth = Math.max(millisWidth,
 179                                    String.format("%d", milliss[i]).length());
 180 
 181             ratios[i] = (double) nanoss[i] / (double) nanoss[0];
 182             ratioWidth = Math.max(ratioWidth,
 183                                   String.format("%.3f", ratios[i]).length());
 184         }
 185 
 186         String format = String.format("%%-%ds %%%dd %%%d.3f%%n",
 187                                       nameWidth, millisWidth, ratioWidth);
 188         String headerFormat = String.format("%%-%ds %%%ds %%%ds%%n",
 189                                             nameWidth, millisWidth, ratioWidth);
 190         System.out.printf(headerFormat, "Method", "Millis", "Ratio");
 191 
 192         // Print out absolute and relative times, calibrated against first job
 193         for (int i = 0; i < size; i++)
 194             System.out.printf(format, jobs.get(i).name(), milliss[i], ratios[i]);
 195     }
 196 
 197     private static String keywordValue(String[] args, String keyword) {
 198         for (String arg : args)
 199             if (arg.startsWith(keyword))
 200                 return arg.substring(keyword.length() + 1);
 201         return null;
 202     }
 203 
 204     private static int intArg(String[] args, String keyword, int defaultValue) {
 205         String val = keywordValue(args, keyword);
 206         return (val == null) ? defaultValue : Integer.parseInt(val);
 207     }
 208 
 209     private static double doubleArg(String[] args, String keyword, double defaultValue) {
 210         String val = keywordValue(args, keyword);
 211         return (val == null) ? defaultValue : Double.parseDouble(val);
 212     }
 213 
 214     private static Pattern patternArg(String[] args, String keyword) {
 215         String val = keywordValue(args, keyword);
 216         return (val == null) ? null : Pattern.compile(val);
 217     }
 218 
 219     private static boolean booleanArg(String[] args, String keyword) {
 220         String val = keywordValue(args, keyword);
 221         if (val == null || val.equals("false")) return false;
 222         if (val.equals("true")) return true;
 223         throw new IllegalArgumentException(val);
 224     }
 225 
 226     private static void deoptimize(int sum) {
 227         if (sum == 42)
 228             System.out.println("the answer");
 229     }
 230 
 231     private static <T> Iterable<T> backwards(final List<T> list) {
 232         return new Iterable<T>() {
 233             public Iterator<T> iterator() {
 234                 return new Iterator<T>() {
 235                     final ListIterator<T> it = list.listIterator(list.size());
 236                     public boolean hasNext() { return it.hasPrevious(); }
 237                     public T next()          { return it.previous(); }
 238                     public void remove()     {        it.remove(); }};}};
 239     }
 240 
 241     // Checks for correctness *and* prevents loop optimizations
 242     static class Check {
 243         private int sum;
 244         public void sum(int sum) {
 245             if (this.sum == 0)
 246                 this.sum = sum;
 247             if (this.sum != sum)
 248                 throw new AssertionError("Sum mismatch");
 249         }
 250     }
 251     volatile Check check = new Check();
 252 
 253     public static void main(String[] args) throws Throwable {
 254         new IteratorMicroBenchmark(args).run();
 255     }
 256 
 257     HashMap<Class<?>, String> goodClassName = new HashMap<>();
 258 
 259     String goodClassName(Class<?> klazz) {
 260         return goodClassName.computeIfAbsent(
 261             klazz,
 262             k -> {
 263                 String simple = k.getSimpleName();
 264                 return (simple.equals("SubList")) // too simple!
 265                     ? k.getName().replaceFirst(".*\\.", "")
 266                     : simple;
 267             });
 268     }
 269 
 270     String goodClassName(Object x) {
 271         return goodClassName(x.getClass());
 272     }
 273 
 274     static List<Integer> makeSubList(
 275         List<Integer> elements,
 276         UnaryOperator<List<Integer>> copyConstructor) {
 277         final ArrayList<Integer> padded = new ArrayList<>();
 278         final ThreadLocalRandom rnd = ThreadLocalRandom.current();
 279         final int frontPorch = rnd.nextInt(3);
 280         final int backPorch = rnd.nextInt(3);
 281         for (int n = frontPorch; n--> 0; ) padded.add(rnd.nextInt());
 282         padded.addAll(elements);
 283         for (int n = backPorch; n--> 0; ) padded.add(rnd.nextInt());
 284         return copyConstructor.apply(padded)
 285             .subList(frontPorch, frontPorch + elements.size());
 286     }
 287 
 288     void run() throws Throwable {
 289         final ArrayList<Integer> al = new ArrayList<>(size);
 290 
 291         // Populate collections with random data
 292         final ThreadLocalRandom rnd = ThreadLocalRandom.current();
 293         for (int i = 0; i < size; i++)
 294             al.add(rnd.nextInt(size));
 295 
 296         final ArrayDeque<Integer> ad = new ArrayDeque<>(al);
 297         final ArrayBlockingQueue<Integer> abq = new ArrayBlockingQueue<>(al.size());
 298         abq.addAll(al);
 299 
 300         // shuffle circular array elements so they wrap
 301         for (int i = 0, n = rnd.nextInt(size); i < n; i++) {
 302             ad.addLast(ad.removeFirst());
 303             abq.add(abq.remove());
 304         }
 305 
 306         final Integer[] array = al.toArray(new Integer[0]);
 307         final List<Integer> immutableSubList
 308             = makeSubList(al, x -> List.of(x.toArray(new Integer[0])));
 309 
 310         Stream<Collection<Integer>> collections = concatStreams(
 311             Stream.of(
 312                 // Lists and their subLists
 313                 al,
 314                 makeSubList(al, ArrayList::new),
 315                 new Vector<>(al),
 316                 makeSubList(al, Vector::new),
 317                 new LinkedList<>(al),
 318                 makeSubList(al, LinkedList::new),
 319                 new CopyOnWriteArrayList<>(al),
 320                 makeSubList(al, CopyOnWriteArrayList::new),
 321 
 322                 ad,
 323                 new PriorityQueue<>(al),
 324                 new ConcurrentLinkedQueue<>(al),
 325                 new ConcurrentLinkedDeque<>(al),
 326 
 327                 // Blocking Queues
 328                 abq,
 329                 new LinkedBlockingQueue<>(al),
 330                 new LinkedBlockingDeque<>(al),
 331                 new LinkedTransferQueue<>(al),
 332                 new PriorityBlockingQueue<>(al),
 333 
 334                 List.of(al.toArray(new Integer[0]))),
 335 
 336             // avoid UnsupportedOperationException in jdk9 and jdk10
 337             (goodClassName(immutableSubList).equals("RandomAccessSubList"))
 338             ? Stream.empty()
 339             : Stream.of(immutableSubList));
 340 
 341         ArrayList<Job> jobs = collections
 342             .flatMap(x -> jobs(x))
 343             .filter(job ->
 344                 nameFilter == null || nameFilter.matcher(job.name()).find())
 345             .collect(toCollection(ArrayList::new));
 346 
 347         if (reverse) Collections.reverse(jobs);
 348         if (shuffle) Collections.shuffle(jobs);
 349 
 350         time(jobs);
 351     }
 352 
 353     @SafeVarargs @SuppressWarnings("varargs")
 354     private static <T> Stream<T> concatStreams(Stream<T> ... streams) {
 355         return Stream.of(streams).flatMap(s -> s);
 356     }
 357 
 358     boolean isMutable(Collection<Integer> x) {
 359         return !(x.getClass().getName().contains("ImmutableCollections$"));
 360     }
 361 
 362     Stream<Job> jobs(Collection<Integer> x) {
 363         return concatStreams(
 364             collectionJobs(x),
 365 
 366             (isMutable(x))
 367             ? mutableCollectionJobs(x)
 368             : Stream.empty(),
 369 
 370             (x instanceof Deque)
 371             ? dequeJobs((Deque<Integer>)x)
 372             : Stream.empty(),
 373 
 374             (x instanceof List)
 375             ? listJobs((List<Integer>)x)
 376             : Stream.empty(),
 377 
 378             (x instanceof List && isMutable(x))
 379             ? mutableListJobs((List<Integer>)x)
 380             : Stream.empty());
 381     }
 382 
 383     Object sneakyAdder(int[] sneakySum) {
 384         return new Object() {
 385             public int hashCode() { throw new AssertionError(); }
 386             public boolean equals(Object z) {
 387                 sneakySum[0] += (int) z; return false; }};
 388     }
 389 
 390     Stream<Job> collectionJobs(Collection<Integer> x) {
 391         final String klazz = goodClassName(x);
 392         return Stream.of(
 393             new Job(klazz + " iterate for loop") {
 394                 public void work() throws Throwable {
 395                     for (int i = 0; i < iterations; i++) {
 396                         int sum = 0;
 397                         for (Integer n : x)
 398                             sum += n;
 399                         check.sum(sum);}}},
 400             new Job(klazz + " iterator().forEachRemaining()") {
 401                 public void work() throws Throwable {
 402                     int[] sum = new int[1];
 403                     for (int i = 0; i < iterations; i++) {
 404                         sum[0] = 0;
 405                         x.iterator().forEachRemaining(n -> sum[0] += n);
 406                         check.sum(sum[0]);}}},
 407             new Job(klazz + " spliterator().tryAdvance()") {
 408                 public void work() throws Throwable {
 409                     int[] sum = new int[1];
 410                     for (int i = 0; i < iterations; i++) {
 411                         sum[0] = 0;
 412                         Spliterator<Integer> spliterator = x.spliterator();
 413                         do {} while (spliterator.tryAdvance(n -> sum[0] += n));
 414                         check.sum(sum[0]);}}},
 415             new Job(klazz + " spliterator().forEachRemaining()") {
 416                 public void work() throws Throwable {
 417                     int[] sum = new int[1];
 418                     for (int i = 0; i < iterations; i++) {
 419                         sum[0] = 0;
 420                         x.spliterator().forEachRemaining(n -> sum[0] += n);
 421                         check.sum(sum[0]);}}},
 422             new Job(klazz + " contains") {
 423                 public void work() throws Throwable {
 424                     int[] sum = new int[1];
 425                     Object sneakyAdder = sneakyAdder(sum);
 426                     for (int i = 0; i < iterations; i++) {
 427                         sum[0] = 0;
 428                         if (x.contains(sneakyAdder)) throw new AssertionError();
 429                         check.sum(sum[0]);}}},
 430             new Job(klazz + " containsAll") {
 431                 public void work() throws Throwable {
 432                     int[] sum = new int[1];
 433                     Collection<Object> sneakyAdderCollection =
 434                         Collections.singleton(sneakyAdder(sum));
 435                     for (int i = 0; i < iterations; i++) {
 436                         sum[0] = 0;
 437                         if (x.containsAll(sneakyAdderCollection))
 438                             throw new AssertionError();
 439                         check.sum(sum[0]);}}},
 440             new Job(klazz + " forEach") {
 441                 public void work() throws Throwable {
 442                     int[] sum = new int[1];
 443                     for (int i = 0; i < iterations; i++) {
 444                         sum[0] = 0;
 445                         x.forEach(n -> sum[0] += n);
 446                         check.sum(sum[0]);}}},
 447             new Job(klazz + " toArray()") {
 448                 public void work() throws Throwable {
 449                     int[] sum = new int[1];
 450                     for (int i = 0; i < iterations; i++) {
 451                         sum[0] = 0;
 452                         for (Object o : x.toArray())
 453                             sum[0] += (Integer) o;
 454                         check.sum(sum[0]);}}},
 455             new Job(klazz + " toArray(a)") {
 456                 public void work() throws Throwable {
 457                     Integer[] a = new Integer[x.size()];
 458                     int[] sum = new int[1];
 459                     for (int i = 0; i < iterations; i++) {
 460                         sum[0] = 0;
 461                         x.toArray(a);
 462                         for (Object o : a)
 463                             sum[0] += (Integer) o;
 464                         check.sum(sum[0]);}}},
 465             new Job(klazz + " toArray(empty)") {
 466                 public void work() throws Throwable {
 467                     Integer[] empty = new Integer[0];
 468                     int[] sum = new int[1];
 469                     for (int i = 0; i < iterations; i++) {
 470                         sum[0] = 0;
 471                         for (Integer o : x.toArray(empty))
 472                             sum[0] += o;
 473                         check.sum(sum[0]);}}},
 474             new Job(klazz + " stream().forEach") {
 475                 public void work() throws Throwable {
 476                     int[] sum = new int[1];
 477                     for (int i = 0; i < iterations; i++) {
 478                         sum[0] = 0;
 479                         x.stream().forEach(n -> sum[0] += n);
 480                         check.sum(sum[0]);}}},
 481             new Job(klazz + " stream().mapToInt") {
 482                 public void work() throws Throwable {
 483                     for (int i = 0; i < iterations; i++) {
 484                         check.sum(x.stream().mapToInt(e -> e).sum());}}},
 485             new Job(klazz + " stream().collect") {
 486                 public void work() throws Throwable {
 487                     for (int i = 0; i < iterations; i++) {
 488                         check.sum(x.stream()
 489                                   .collect(summingInt(e -> e)));}}},
 490             new Job(klazz + " stream()::iterator") {
 491                 public void work() throws Throwable {
 492                     int[] sum = new int[1];
 493                     for (int i = 0; i < iterations; i++) {
 494                         sum[0] = 0;
 495                         for (Integer o : (Iterable<Integer>) x.stream()::iterator)
 496                             sum[0] += o;
 497                         check.sum(sum[0]);}}},
 498             new Job(klazz + " parallelStream().forEach") {
 499                 public void work() throws Throwable {
 500                     for (int i = 0; i < iterations; i++) {
 501                         LongAdder sum = new LongAdder();
 502                         x.parallelStream().forEach(n -> sum.add(n));
 503                         check.sum((int) sum.sum());}}},
 504             new Job(klazz + " parallelStream().mapToInt") {
 505                 public void work() throws Throwable {
 506                     for (int i = 0; i < iterations; i++) {
 507                         check.sum(x.parallelStream().mapToInt(e -> e).sum());}}},
 508             new Job(klazz + " parallelStream().collect") {
 509                 public void work() throws Throwable {
 510                     for (int i = 0; i < iterations; i++) {
 511                         check.sum(x.parallelStream()
 512                                   .collect(summingInt(e -> e)));}}},
 513             new Job(klazz + " parallelStream()::iterator") {
 514                 public void work() throws Throwable {
 515                     int[] sum = new int[1];
 516                     for (int i = 0; i < iterations; i++) {
 517                         sum[0] = 0;
 518                         for (Integer o : (Iterable<Integer>) x.parallelStream()::iterator)
 519                             sum[0] += o;
 520                         check.sum(sum[0]);}}});
 521     }
 522 
 523     Stream<Job> mutableCollectionJobs(Collection<Integer> x) {
 524         final String klazz = goodClassName(x);
 525         return Stream.of(
 526             new Job(klazz + " removeIf") {
 527                 public void work() throws Throwable {
 528                     int[] sum = new int[1];
 529                     for (int i = 0; i < iterations; i++) {
 530                         sum[0] = 0;
 531                         if (x.removeIf(n -> { sum[0] += n; return false; }))
 532                             throw new AssertionError();
 533                         check.sum(sum[0]);}}},
 534             new Job(klazz + " remove(Object)") {
 535                 public void work() throws Throwable {
 536                     int[] sum = new int[1];
 537                     Object sneakyAdder = sneakyAdder(sum);
 538                     for (int i = 0; i < iterations; i++) {
 539                         sum[0] = 0;
 540                         if (x.remove(sneakyAdder)) throw new AssertionError();
 541                         check.sum(sum[0]);}}});
 542     }
 543 
 544     Stream<Job> dequeJobs(Deque<Integer> x) {
 545         final String klazz = goodClassName(x);
 546         return Stream.of(
 547             new Job(klazz + " descendingIterator() loop") {
 548                 public void work() throws Throwable {
 549                     for (int i = 0; i < iterations; i++) {
 550                         int sum = 0;
 551                         Iterator<Integer> it = x.descendingIterator();
 552                         while (it.hasNext())
 553                             sum += it.next();
 554                         check.sum(sum);}}},
 555             new Job(klazz + " descendingIterator().forEachRemaining()") {
 556                 public void work() throws Throwable {
 557                     int[] sum = new int[1];
 558                     for (int i = 0; i < iterations; i++) {
 559                         sum[0] = 0;
 560                         x.descendingIterator().forEachRemaining(n -> sum[0] += n);
 561                         check.sum(sum[0]);}}});
 562     }
 563 
 564     Stream<Job> listJobs(List<Integer> x) {
 565         final String klazz = goodClassName(x);
 566         return Stream.of(
 567             new Job(klazz + " listIterator forward loop") {
 568                 public void work() throws Throwable {
 569                     for (int i = 0; i < iterations; i++) {
 570                         int sum = 0;
 571                         ListIterator<Integer> it = x.listIterator();
 572                         while (it.hasNext())
 573                             sum += it.next();
 574                         check.sum(sum);}}},
 575             new Job(klazz + " listIterator backward loop") {
 576                 public void work() throws Throwable {
 577                     for (int i = 0; i < iterations; i++) {
 578                         int sum = 0;
 579                         ListIterator<Integer> it = x.listIterator(x.size());
 580                         while (it.hasPrevious())
 581                             sum += it.previous();
 582                         check.sum(sum);}}},
 583             new Job(klazz + " indexOf") {
 584                 public void work() throws Throwable {
 585                     int[] sum = new int[1];
 586                     Object sneakyAdder = sneakyAdder(sum);
 587                     for (int i = 0; i < iterations; i++) {
 588                         sum[0] = 0;
 589                         if (x.indexOf(sneakyAdder) != -1)
 590                             throw new AssertionError();
 591                         check.sum(sum[0]);}}},
 592             new Job(klazz + " lastIndexOf") {
 593                 public void work() throws Throwable {
 594                     int[] sum = new int[1];
 595                     Object sneakyAdder = sneakyAdder(sum);
 596                     for (int i = 0; i < iterations; i++) {
 597                         sum[0] = 0;
 598                         if (x.lastIndexOf(sneakyAdder) != -1)
 599                             throw new AssertionError();
 600                         check.sum(sum[0]);}}},
 601             new Job(klazz + " equals") {
 602                 public void work() throws Throwable {
 603                     ArrayList<Integer> copy = new ArrayList<>(x);
 604                     for (int i = 0; i < iterations; i++) {
 605                         if (!x.equals(copy))
 606                             throw new AssertionError();}}},
 607             new Job(klazz + " hashCode") {
 608                 public void work() throws Throwable {
 609                     int hashCode = Arrays.hashCode(x.toArray());
 610                     for (int i = 0; i < iterations; i++) {
 611                         if (x.hashCode() != hashCode)
 612                             throw new AssertionError();}}});
 613     }
 614 
 615     Stream<Job> mutableListJobs(List<Integer> x) {
 616         final String klazz = goodClassName(x);
 617         return Stream.of(
 618             new Job(klazz + " replaceAll") {
 619                 public void work() throws Throwable {
 620                     int[] sum = new int[1];
 621                     UnaryOperator<Integer> sneakyAdder =
 622                         x -> { sum[0] += x; return x; };
 623                     for (int i = 0; i < iterations; i++) {
 624                         sum[0] = 0;
 625                         x.replaceAll(sneakyAdder);
 626                         check.sum(sum[0]);}}});
 627     }
 628 }