View Javadoc
1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one or more
3    * contributor license agreements.  See the NOTICE file distributed with
4    * this work for additional information regarding copyright ownership.
5    * The ASF licenses this file to You under the Apache License, Version 2.0
6    * (the "License"); you may not use this file except in compliance with
7    * the License.  You may obtain a copy of the License at
8    *
9    *      http://www.apache.org/licenses/LICENSE-2.0
10   *
11   * Unless required by applicable law or agreed to in writing, software
12   * distributed under the License is distributed on an "AS IS" BASIS,
13   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14   * See the License for the specific language governing permissions and
15   * limitations under the License.
16   */
17  
18  package org.apache.commons.statistics.examples.jmh.descriptive;
19  
20  import java.util.ArrayList;
21  import java.util.Arrays;
22  import java.util.EnumSet;
23  import java.util.List;
24  import java.util.Locale;
25  import java.util.Objects;
26  import java.util.concurrent.TimeUnit;
27  import java.util.concurrent.atomic.AtomicInteger;
28  import java.util.function.BiFunction;
29  import java.util.function.BinaryOperator;
30  import java.util.logging.Logger;
31  import org.apache.commons.rng.UniformRandomProvider;
32  import org.apache.commons.rng.sampling.ArraySampler;
33  import org.apache.commons.rng.sampling.PermutationSampler;
34  import org.apache.commons.rng.sampling.distribution.DiscreteUniformSampler;
35  import org.apache.commons.rng.sampling.distribution.SharedStateDiscreteSampler;
36  import org.apache.commons.rng.simple.RandomSource;
37  import org.apache.commons.statistics.descriptive.Quantile;
38  import org.openjdk.jmh.annotations.Benchmark;
39  import org.openjdk.jmh.annotations.BenchmarkMode;
40  import org.openjdk.jmh.annotations.Fork;
41  import org.openjdk.jmh.annotations.Level;
42  import org.openjdk.jmh.annotations.Measurement;
43  import org.openjdk.jmh.annotations.Mode;
44  import org.openjdk.jmh.annotations.OutputTimeUnit;
45  import org.openjdk.jmh.annotations.Param;
46  import org.openjdk.jmh.annotations.Scope;
47  import org.openjdk.jmh.annotations.Setup;
48  import org.openjdk.jmh.annotations.State;
49  import org.openjdk.jmh.annotations.Warmup;
50  import org.openjdk.jmh.infra.Blackhole;
51  
52  /**
53   * Executes a benchmark of the creation of a quantile from array data.
54   */
55  @BenchmarkMode(Mode.AverageTime)
56  @OutputTimeUnit(TimeUnit.NANOSECONDS)
57  @Warmup(iterations = 5, time = 1, timeUnit = TimeUnit.SECONDS)
58  @Measurement(iterations = 5, time = 1, timeUnit = TimeUnit.SECONDS)
59  @State(Scope.Benchmark)
60  @Fork(value = 1, jvmArgs = {"-server", "-Xms512M", "-Xmx8192M"})
61  public class QuantilePerformance {
62      /** Use the JDK sort function. */
63      private static final String JDK = "JDK";
64      /** Commons Math 3 Percentile implementation. */
65      private static final String CM3 = "CM3";
66      /** Commons Math 4 Percentile implementation. */
67      private static final String CM4 = "CM4";
68      /** Commons Statistics implementation. */
69      private static final String STATISTICS = "Statistics";
70  
71      /** Random source. */
72      private static final RandomSource RANDOM_SOURCE = RandomSource.XO_RO_SHI_RO_128_PP;
73  
74      /**
75       * Source of {@code double} array data.
76       *
77       * <p>By default this uses the adverse input test suite from figure 1 in Bentley and McIlroy
78       * (1993) Engineering a sort function, Software, practice and experience, Vol.23(11),
79       * 1249–1265.
80       *
81       * <p>An alternative set of data is from Valois (2000) Introspective sorting and selection
82       * revisited, Software, practice and experience, Vol.30(6), 617-638.
83       *
84       * <p>Note
85       *
86       * <p>This data source has been adapted from {@code o.a.c.numbers.examples.jmh.arrays}.
87       *
88       * <p>Random distribution mode
89       *
90       * <p>The default BM configuration includes random samples generated as a family of
91       * single samples created from ranges that are powers of two [0, 2^i). This small set
92       * of samples is only a small representation of randomness. For small lengths this may
93       * only be a few random samples.
94       *
95       * <p>The data source can be changed to generate a fixed number of random samples
96       * using a uniform distribution [0, n]. For this purpose the distribution must be set
97       * to {@link Distribution#RANDOM} and the {@code samples} set above
98       * zero. The inclusive upper bound {@code n} is set using the {@code seed}.
99       * If this is zero then the default is {@link Integer#MAX_VALUE}.
100      */
101     @State(Scope.Benchmark)
102     public abstract static class AbstractDataSource {
103         /** All distributions / modifications. */
104         private static final String ALL = "all";
105         /** All distributions / modifications in the Bentley and McIlroy test suite. */
106         private static final String BM = "bm";
107         /** All distributions in the Valois test suite. These currently ignore the seed.
108          * To replicate Valois used a fixed seed and the copy modification. */
109         private static final String VALOIS = "valois";
110         /** Flag to determine if the data size should be logged. This is useful to be
111          * able to determine the execution time per sample when the number of samples
112          * is dynamically created based on the data length, range and seed. */
113         private static final AtomicInteger LOG_SIZE = new AtomicInteger();
114 
115         /**
116          * The type of distribution.
117          */
118         enum Distribution {
119             // B&M (1993)
120 
121             /** sawtooth distribution. */
122             SAWTOOTH,
123             /** random distribution. */
124             RANDOM,
125             /** stagger distribution. */
126             STAGGER,
127             /** plateau distribution. */
128             PLATEAU,
129             /** shuffle distribution. */
130             SHUFFLE,
131 
132             /** sharktooth distribution. This is an addition to the original suite of B & M
133              * and is not included in the test suite by default and must be specified.
134              *
135              * <p>An ascending then descending sequence is also known as organpipe in
136              * Valois (2000),
137              * Introspective sorting and selection revisited,
138              * Software–Practice and Experience 30, 617–638.
139              * This version allows multiple ascending/descending runs in the same length. */
140             SHARKTOOTH,
141 
142             // Valois (2000)
143 
144             /** Sorted. */
145             SORTED,
146             /** Permutation of ones and zeros. */
147             ONEZERO,
148             /** Musser's median-of-3 killer. */
149             M3KILLER,
150             /** A sorted sequence rotated left once. */
151             ROTATED,
152             /** Musser's two-faced sequence (the median-of-3 killer with two random permutations). */
153             TWOFACED,
154             /** An ascending then descending sequence. */
155             ORGANPIPE;
156         }
157 
158         /**
159          * The type of data modification.
160          */
161         enum Modification {
162             /** copy modification. */
163             COPY,
164             /** reverse modification. */
165             REVERSE,
166             /** reverse front-half modification. */
167             REVERSE_FRONT,
168             /** reverse back-half modification. */
169             REVERSE_BACK,
170             /** sort modification. */
171             SORT,
172             /** descending modification (this is an addition to the original suite of B & M).
173              * It is useful for testing worst case performance, e.g. insertion sort performs
174              * poorly on descending data. Heapselect using a max heap would perform poorly
175              * if data is processed in the forward direction as all elements must be inserted.
176              *
177              * <p>This is not included in the test suite by default and must be specified.
178              * Note that the Shuffle distribution with a very large seed 'm' is effectively an
179              * ascending sequence and will be reversed to descending as part of the original
180              * B&M suite of data. */
181             DESCENDING,
182             /** dither modification. */
183             DITHER;
184         }
185 
186         /** Order. This is randomized to ensure that successive calls do not partition
187          * similar distributions. Randomized per invocation to avoid the JVM 'learning'
188          * branch decisions to take in small data sets. */
189         protected int[] order;
190         /** Cached source of randomness. */
191         protected UniformRandomProvider rng;
192 
193         /** Type of data. Multiple types can be specified in the same string using
194          * lower/upper case, delimited using ':'. */
195         @Param({BM})
196         private String distribution = BM;
197 
198         /** Type of data modification. Multiple types can be specified in the same string using
199          * lower/upper case, delimited using ':'. */
200         @Param({BM})
201         private String modification = BM;
202 
203         /** Extra range to add to the data length.
204          * E.g. Use 1 to force use of odd and even length samples for the median. */
205         @Param({"1"})
206         private int range = 1;
207 
208         /** Sample 'seed'. This is {@code m} in Bentley and McIlroy's test suite.
209          * If set to zero the default is to use powers of 2 based on sample size. */
210         @Param({"0"})
211         private int seed;
212 
213         /** Sample offset. This is used to shift each distribution to create different data.
214          * It is advanced on each invocation of {@link #setup()}. */
215         @Param({"0"})
216         private int offset;
217 
218         /** Number of samples. Applies only to the random distribution. In this case
219          * the length of the data is randomly chosen in {@code [length, length + range)}. */
220         @Param({"0"})
221         private int samples;
222 
223         /** RNG seed. Created using ThreadLocalRandom.current().nextLong(). This is advanced
224          * for the random distribution mode per iteration. Each benchmark executed by
225          * JMH will use the same random data, even across JVMs.
226          *
227          * <p>If this is zero then a random seed is chosen. */
228         @Param({"-7450238124206088695"})
229         private long rngSeed = -7450238124206088695L;
230 
231         /** Data. This is stored as integer data which saves memory. Note that when ranking
232          * data it is not necessary to have the full range of the double data type; the same
233          * number of unique values can be recorded in an array using an integer type.
234          * Returning a double[] forces a copy to be generated for destructive sorting /
235          * partitioning methods. */
236         private int[][] data;
237 
238         /**
239          * Gets the sample for the given {@code index}.
240          *
241          * <p>This is returned in a randomized order per iteration.
242          *
243          * @param index Index.
244          * @return the data sample
245          */
246         public double[] getData(int index) {
247             return getDataSample(order[index]);
248         }
249 
250         /**
251          * Gets the sample for the given {@code index}.
252          *
253          * <p>This is returned in a randomized order per iteration.
254          *
255          * @param index Index.
256          * @return the data sample
257          */
258         public int[] getIntData(int index) {
259             return getIntDataSample(order[index]);
260         }
261 
262         /**
263          * Gets the sample for the given {@code index}.
264          *
265          * @param index Index.
266          * @return the data sample
267          */
268         protected double[] getDataSample(int index) {
269             final int[] a = data[index];
270             final double[] x = new double[a.length];
271             for (int i = -1; ++i < a.length;) {
272                 x[i] = a[i];
273             }
274             return x;
275         }
276 
277         /**
278          * Gets the sample for the given {@code index}.
279          *
280          * @param index Index.
281          * @return the data sample
282          */
283         protected int[] getIntDataSample(int index) {
284             // For parity with other methods do not use data.clone()
285             final int[] a = data[index];
286             final int[] x = new int[a.length];
287             for (int i = -1; ++i < a.length;) {
288                 x[i] = a[i];
289             }
290             return x;
291         }
292 
293         /**
294          * Gets the sample size for the given {@code index}.
295          *
296          * @param index Index.
297          * @return the data sample size
298          */
299         public int getDataSize(int index) {
300             return data[index].length;
301         }
302 
303         /**
304          * Get the number of data samples.
305          *
306          * <p>Note: This data source will create a permutation order per invocation based on
307          * this size. Per-invocation control in JMH is recommended for methods that take
308          * more than 1 millisecond to execute. For very small data and/or fast methods
309          * this may not be achievable. Child classes may override this value to create
310          * a large number of repeats of the same data per invocation. Any class performing
311          * this should also override {@link #getData(int)} to prevent index out of bound errors.
312          * This can be done by mapping the index to the original index using the number of repeats
313          * e.g. {@code original index = index / repeats}.
314          *
315          * @return the number of samples
316          */
317         public int size() {
318             return data.length;
319         }
320 
321         /**
322          * Create the data.
323          */
324         @Setup(Level.Iteration)
325         public void setup() {
326             Objects.requireNonNull(distribution);
327             Objects.requireNonNull(modification);
328 
329             // Set-up using parameters (may throw)
330             final EnumSet<Distribution> dist = getDistributions();
331             final int length = getLength();
332             if (length < 1) {
333                 throw new IllegalStateException("Unsupported length: " + length);
334             }
335             // Note: Bentley-McIlroy use n in {100, 1023, 1024, 1025}.
336             // Here we only support a continuous range. The range is important
337             // for the median as it will require one or two points to partition
338             // if the length is odd or even.
339             final int r = range > 0 ? range : 0;
340             if (length + (long) r > Integer.MAX_VALUE) {
341                 throw new IllegalStateException("Unsupported upper length: " + length);
342             }
343             final int length2 = length + r;
344 
345             // Allow pseudorandom seeding
346             if (rngSeed == 0) {
347                 rngSeed = RandomSource.createLong();
348             }
349             if (rng == null) {
350                 // First call, create objects
351                 rng = RANDOM_SOURCE.create(rngSeed);
352             }
353 
354             // Special case for random distribution mode
355             if (dist.contains(Distribution.RANDOM) && dist.size() == 1 && samples > 0) {
356                 data = new int[samples][];
357                 final int upper = seed > 0 ? seed : Integer.MAX_VALUE;
358                 final SharedStateDiscreteSampler s1 = DiscreteUniformSampler.of(rng, 0, upper);
359                 final SharedStateDiscreteSampler s2 = DiscreteUniformSampler.of(rng, length, length2);
360                 for (int i = 0; i < data.length; i++) {
361                     final int[] a = new int[s2.sample()];
362                     for (int j = a.length; --j >= 0;) {
363                         a[j] = s1.sample();
364                     }
365                     data[i] = a;
366                 }
367                 return;
368             }
369 
370             // New data per iteration
371             data = null;
372             final int o = offset;
373             offset = rng.nextInt();
374 
375             final EnumSet<Modification> mod = getModifications();
376 
377             // Data using the RNG will be randomized only once.
378             // Here we use the same seed for parity across methods.
379             // Note that most distributions do not use the source of randomness.
380             final ArrayList<int[]> sampleData = new ArrayList<>();
381             for (int n = length; n <= length2; n++) {
382                 // Note: Large lengths may wish to limit the range of m to limit
383                 // the memory required to store the samples. Currently a single
384                 // m is supported via the seed parameter.
385                 // Default seed will create ceil(log2(2*n)) * 5 dist * 6 mods samples:
386                 // MAX  = 32 * 5 * 7 * (2^31-1) * 4 bytes == 7679 GiB
387                 // HUGE = 31 * 5 * 7 * 2^30 * 4 bytes == 3719 GiB
388                 // BIG  = 21 * 5 * 7 * 2^20 * 4 bytes == 2519 MiB  <-- within configured JVM -Xmx
389                 // MED  = 11 * 5 * 7 * 2^10 * 4 bytes == 1318 KiB
390                 // (This excludes the descending modification.)
391                 // It is possible to create lengths above 2^30 using a single distribution,
392                 // modification, and seed:
393                 // MAX1 = 1 * 1 * 1 * (2^31-1) * 4 bytes == 8191 MiB
394                 // However this is then used to create double[] data thus requiring an extra
395                 // ~16GiB memory for the sample to partition.
396                 for (final int m : createSeeds(seed, n)) {
397                     final List<int[]> d = createDistributions(dist, rng, n, m, o);
398                     for (int i = 0; i < d.size(); i++) {
399                         final int[] x = d.get(i);
400                         if (mod.contains(Modification.COPY)) {
401                             // Don't copy! All other methods generate copies
402                             // so we can use this in-place.
403                             sampleData.add(x);
404                         }
405                         if (mod.contains(Modification.REVERSE)) {
406                             sampleData.add(reverse(x, 0, n));
407                         }
408                         if (mod.contains(Modification.REVERSE_FRONT)) {
409                             sampleData.add(reverse(x, 0, n >>> 1));
410                         }
411                         if (mod.contains(Modification.REVERSE_BACK)) {
412                             sampleData.add(reverse(x, n >>> 1, n));
413                         }
414                         // Only sort once
415                         if (mod.contains(Modification.SORT) ||
416                             mod.contains(Modification.DESCENDING)) {
417                             final int[] y = x.clone();
418                             Arrays.sort(y);
419                             if (mod.contains(Modification.DESCENDING)) {
420                                 sampleData.add(reverse(y, 0, n));
421                             }
422                             if (mod.contains(Modification.SORT)) {
423                                 sampleData.add(y);
424                             }
425                         }
426                         if (mod.contains(Modification.DITHER)) {
427                             sampleData.add(dither(x));
428                         }
429                     }
430                 }
431             }
432             data = sampleData.toArray(int[][]::new);
433             if (LOG_SIZE.getAndSet(length) != length) {
434                 Logger.getLogger(getClass().getName()).info(
435                     () -> String.format("Data length: [%d, %d] n=%d", length, length2, data.length));
436             }
437         }
438 
439         /**
440          * Create the order to process the indices.
441          *
442          * <p>JMH recommends that invocations should take at
443          * least 1 millisecond for timings to be usable. In practice there should be
444          * enough data that processing takes much longer than a few milliseconds.
445          */
446         @Setup(Level.Invocation)
447         public void createOrder() {
448             if (order == null) {
449                 // First call, create objects
450                 order = PermutationSampler.natural(size());
451             }
452             ArraySampler.shuffle(rng, order);
453         }
454 
455         /**
456          * @return the distributions
457          */
458         private EnumSet<Distribution> getDistributions() {
459             EnumSet<Distribution> dist;
460             if (BM.equals(distribution)) {
461                 dist = EnumSet.of(
462                     Distribution.SAWTOOTH,
463                     Distribution.RANDOM,
464                     Distribution.STAGGER,
465                     Distribution.PLATEAU,
466                     Distribution.SHUFFLE);
467             } else if (VALOIS.equals(distribution)) {
468                 dist = EnumSet.of(
469                     Distribution.RANDOM,
470                     Distribution.SORTED,
471                     Distribution.ONEZERO,
472                     Distribution.M3KILLER,
473                     Distribution.ROTATED,
474                     Distribution.TWOFACED,
475                     Distribution.ORGANPIPE);
476             } else {
477                 dist = getEnumFromParam(Distribution.class, distribution);
478             }
479             return dist;
480         }
481 
482         /**
483          * @return the modifications
484          */
485         private EnumSet<Modification> getModifications() {
486             EnumSet<Modification> mod;
487             if (BM.equals(modification)) {
488                 // Modifications are from Bentley and McIlroy
489                 mod = EnumSet.allOf(Modification.class);
490                 // ... except descending
491                 mod.remove(Modification.DESCENDING);
492             } else if (VALOIS.equals(modification)) {
493                 // For convenience alias Valois to copy
494                 mod = EnumSet.of(Modification.COPY);
495             } else {
496                 mod = getEnumFromParam(Modification.class, modification);
497             }
498             return mod;
499         }
500 
501         /**
502          * Gets all the enum values of the given class from the parameters.
503          *
504          * @param <E> Enum type.
505          * @param cls Class of the enum.
506          * @param parameters Parameters (multiple values delimited by ':')
507          * @return the enum values
508          */
509         static <E extends Enum<E>> EnumSet<E> getEnumFromParam(Class<E> cls, String parameters) {
510             if (ALL.equals(parameters)) {
511                 return EnumSet.allOf(cls);
512             }
513             final EnumSet<E> set = EnumSet.noneOf(cls);
514             final String s = parameters.toUpperCase(Locale.ROOT);
515             for (final E e : cls.getEnumConstants()) {
516                 // Scan for the name
517                 for (int i = s.indexOf(e.name(), 0); i >= 0; i = s.indexOf(e.name(), i)) {
518                     // Ensure a full match to the name:
519                     // either at the end of the string, or followed by the delimiter
520                     i += e.name().length();
521                     if (i == s.length() || s.charAt(i) == ':') {
522                         set.add(e);
523                         break;
524                     }
525                 }
526             }
527             if (set.isEmpty()) {
528                 throw new IllegalStateException("Unknown parameters: " + parameters);
529             }
530             return set;
531         }
532 
533         /**
534          * Creates the seeds.
535          *
536          * <p>This can be pasted into a JShell terminal to verify it works for any size
537          * {@code 1 <= n < 2^31}. With the default behaviour all seeds {@code m} are
538          * strictly positive powers of 2 and the highest seed should be below {@code 2*n}.
539          *
540          * @param seed Seed (use 0 for default; or provide a strictly positive {@code 1 <= m <= 2^31}).
541          * @param n Sample size.
542          * @return the seeds
543          */
544         private static int[] createSeeds(int seed, int n) {
545             // Allow [1, 2^31] (note 2^31 is negative but handled as a power of 2)
546             if (seed - 1 >= 0) {
547                 return new int[] {seed};
548             }
549             // Bentley-McIlroy use:
550             // for: m = 1; m < 2 * n; m *= 2
551             // This has been modified here to handle n up to MAX_VALUE
552             // by knowing the count of m to generate as the power of 2 >= n.
553 
554             // ceil(log2(n)) + 1 == ceil(log2(2*n)) but handles MAX_VALUE
555             int c = 33 - Integer.numberOfLeadingZeros(n - 1);
556             final int[] seeds = new int[c];
557             c = 0;
558             for (int m = 1; c != seeds.length; m *= 2) {
559                 seeds[c++] = m;
560             }
561             return seeds;
562         }
563 
564         /**
565          * Creates the distribution samples. Handles {@code m = 2^31} using
566          * {@link Integer#MIN_VALUE}.
567          *
568          * <p>The offset is used to adjust each distribution to generate a different
569          * output. Only applies to distributions that do not use the source of randomness.
570          *
571          * <p>Distributions are generated in enum order and recorded in the output {@code sampleDist}.
572          * Distributions that are a constant value at {@code m == 1} are not generated.
573          * This case is handled by the plateau distribution which will be a constant value
574          * except one occurrence of zero.
575          *
576          * @param dist Distributions.
577          * @param rng Source of randomness.
578          * @param n Length of the sample.
579          * @param m Sample seed (in [1, 2^31])
580          * @param o Offset.
581          * @return the samples
582          */
583         private static List<int[]> createDistributions(EnumSet<Distribution> dist,
584                 UniformRandomProvider rng, int n, int m, int o) {
585             final ArrayList<int[]> distData = new ArrayList<>(6);
586             int[] x;
587             // B&M (1993)
588             if (dist.contains(Distribution.SAWTOOTH) && m != 1) {
589                 distData.add(x = new int[n]);
590                 // i % m
591                 // Typical case m is a power of 2 so we can use a mask
592                 // Use the offset.
593                 final int mask = m - 1;
594                 if ((m & mask) == 0) {
595                     for (int i = -1; ++i < n;) {
596                         x[i] = (i + o) & mask;
597                     }
598                 } else {
599                     // User input seed. Start at the offset.
600                     int j = Integer.remainderUnsigned(o, m);
601                     for (int i = -1; ++i < n;) {
602                         j = j % m;
603                         x[i] = j++;
604                     }
605                 }
606             }
607             if (dist.contains(Distribution.RANDOM) && m != 1) {
608                 distData.add(x = new int[n]);
609                 // rand() % m
610                 // A sampler is faster than rng.nextInt(m); the sampler has an inclusive upper.
611                 final SharedStateDiscreteSampler s = DiscreteUniformSampler.of(rng, 0, m - 1);
612                 for (int i = -1; ++i < n;) {
613                     x[i] = s.sample();
614                 }
615             }
616             if (dist.contains(Distribution.STAGGER)) {
617                 distData.add(x = new int[n]);
618                 // Overflow safe: (i * m + i) % n
619                 final long nn = n;
620                 final long oo = Integer.toUnsignedLong(o);
621                 for (int i = -1; ++i < n;) {
622                     final long j = i + oo;
623                     x[i] = (int) ((j * m + j) % nn);
624                 }
625             }
626             if (dist.contains(Distribution.PLATEAU)) {
627                 distData.add(x = new int[n]);
628                 // min(i, m)
629                 for (int i = Math.min(n, m); --i >= 0;) {
630                     x[i] = i;
631                 }
632                 for (int i = m - 1; ++i < n;) {
633                     x[i] = m;
634                 }
635                 // Rotate
636                 final int n1 = Integer.remainderUnsigned(o, n);
637                 if (n1 != 0) {
638                     final int[] a = x.clone();
639                     final int n2 = n - n1;
640                     System.arraycopy(a, 0, x, n1, n2);
641                     System.arraycopy(a, n2, x, 0, n1);
642                 }
643             }
644             if (dist.contains(Distribution.SHUFFLE) && m != 1) {
645                 distData.add(x = new int[n]);
646                 // rand() % m ? (j += 2) : (k += 2)
647                 final SharedStateDiscreteSampler s = DiscreteUniformSampler.of(rng, 0, m - 1);
648                 for (int i = -1, j = 0, k = 1; ++i < n;) {
649                     x[i] = s.sample() != 0 ? (j += 2) : (k += 2);
650                 }
651             }
652             // Extra - based on organpipe with a variable ascending/descending length
653             if (dist.contains(Distribution.SHARKTOOTH) && m != 1) {
654                 distData.add(x = new int[n]);
655                 // ascending-descending runs
656                 int i = -1;
657                 int j = (o & Integer.MAX_VALUE) % m - 1;
658                 OUTER:
659                 for (;;) {
660                     while (++j < m) {
661                         if (++i == n) {
662                             break OUTER;
663                         }
664                         x[i] = j;
665                     }
666                     while (--j >= 0) {
667                         if (++i == n) {
668                             break OUTER;
669                         }
670                         x[i] = j;
671                     }
672                 }
673             }
674             // Valois (2000)
675             if (dist.contains(Distribution.SORTED)) {
676                 distData.add(x = new int[n]);
677                 for (int i = -1; ++i < n;) {
678                     x[i] = i;
679                 }
680             }
681             if (dist.contains(Distribution.ONEZERO)) {
682                 distData.add(x = new int[n]);
683                 // permutation of floor(n/2) ones and ceil(n/2) zeroes.
684                 // For convenience this uses random ones and zeros to avoid a shuffle
685                 // and simply reads bits from integers. The distribution will not
686                 // be exactly 50:50.
687                 final int end = n & ~31;
688                 for (int i = 0; i < end; i += 32) {
689                     int z = rng.nextInt();
690                     for (int j = -1; ++j < 32;) {
691                         x[i + j] = z & 1;
692                         z >>>= 1;
693                     }
694                 }
695                 for (int i = end; ++i < n;) {
696                     x[i] = rng.nextBoolean() ? 1 : 0;
697                 }
698             }
699             if (dist.contains(Distribution.M3KILLER)) {
700                 distData.add(x = new int[n]);
701                 medianOf3Killer(x);
702             }
703             if (dist.contains(Distribution.ROTATED)) {
704                 distData.add(x = new int[n]);
705                 // sorted sequence rotated left once
706                 // 1, 2, 3, ..., n-1, n, 0
707                 for (int i = 0; i < n;) {
708                     x[i] = ++i;
709                 }
710                 x[n - 1] = 0;
711             }
712             if (dist.contains(Distribution.TWOFACED)) {
713                 distData.add(x = new int[n]);
714                 // Musser's two faced randomly permutes a median-of-3 killer in
715                 // 4 floor(log2(n)) through n/2 and n/2 + 4 floor(log2(n)) through n
716                 medianOf3Killer(x);
717                 final int j = 4 * (31 - Integer.numberOfLeadingZeros(n));
718                 final int n2 = n >>> 1;
719                 ArraySampler.shuffle(rng, x, j, n2);
720                 ArraySampler.shuffle(rng, x, n2 + j, n);
721             }
722             if (dist.contains(Distribution.ORGANPIPE)) {
723                 distData.add(x = new int[n]);
724                 // 0, 1, 2, 3, ..., 3, 2, 1, 0
725                 // n should be even to leave two equal values in the middle, otherwise a single
726                 for (int i = -1, j = n; ++i <= --j;) {
727                     x[i] = i;
728                     x[j] = i;
729                 }
730             }
731             return distData;
732         }
733 
734         /**
735          * Create Musser's median-of-3 killer sequence (in-place).
736          *
737          * @param x Data.
738          */
739         private static void medianOf3Killer(int[] x) {
740             // This uses the original K_2k sequence from Musser (1997)
741             // Introspective sorting and selection algorithms,
742             // Software—Practice and Experience, 27(8), 983–993.
743             // A true median-of-3 killer requires n to be an even integer divisible by 4,
744             // i.e. k is an even positive integer. This causes a median-of-3 partition
745             // strategy to produce a sequence of n/4 partitions into sub-sequences of
746             // length 2 and n-2, 2 and n-4, ..., 2 and n/2.
747             // 1   2   3   4   5       k-2   k-1  k   k+1 k+2 k+3     2k-1  2k
748             // 1, k+1, 3, k+3, 5, ..., 2k-3, k-1 2k-1  2   4   6  ... 2k-2  2k
749             final int n = x.length;
750             final int k = n >>> 1;
751             for (int i = 0; i < k; i++) {
752                 x[i] = ++i;
753                 x[i] = k + i;
754             }
755             for (int i = k - 1, j = 2; ++i < n; j += 2) {
756                 x[i] = j;
757             }
758         }
759 
760         /**
761          * Return a (part) reversed copy of the data.
762          *
763          * @param x Data.
764          * @param from Start index to reverse (inclusive).
765          * @param to End index to reverse (exclusive).
766          * @return the copy
767          */
768         private static int[] reverse(int[] x, int from, int to) {
769             final int[] a = x.clone();
770             for (int i = from - 1, j = to; ++i < --j;) {
771                 final int v = a[i];
772                 a[i] = a[j];
773                 a[j] = v;
774             }
775             return a;
776         }
777 
778         /**
779          * Return a dithered copy of the data.
780          *
781          * @param x Data.
782          * @return the copy
783          */
784         private static int[] dither(int[] x) {
785             final int[] a = x.clone();
786             for (int i = a.length; --i >= 0;) {
787                 // Bentley-McIlroy use i % 5.
788                 // It is important this is not a power of 2 so it will not coincide
789                 // with patterns created in the data using the default m powers-of-2.
790                 a[i] += i % 5;
791             }
792             return a;
793         }
794 
795         /**
796          * Gets the minimum length of the data.
797          * The actual length is enumerated in {@code [length, length + range]}.
798          *
799          * @return the length
800          */
801         protected abstract int getLength();
802 
803         /**
804          * Gets the range.
805          *
806          * @return the range
807          */
808         final int getRange() {
809             return range;
810         }
811     }
812 
813     /**
814      * Source of {@code double} array data.
815      */
816     @State(Scope.Benchmark)
817     public static class DataSource extends AbstractDataSource {
818         /** Data length. */
819         @Param({"1000", "100000"})
820         private int length;
821 
822         /** {@inheritDoc} */
823         @Override
824         protected int getLength() {
825             return length;
826         }
827     }
828 
829     /**
830      * Source of quantiles.
831      */
832     @State(Scope.Benchmark)
833     public static class QuantileSource {
834         /** Quantiles.
835          * Delimited by ':' to allow use via the JMH command-line parser which
836          * uses ',' as the delimiter. */
837         @Param({"0.25:0.5:0.75",
838                 "0.01:0.99",
839                 "1e-100:1.0", // min,max: CM implementations do not allow p=0.0
840                 "0.25:0.75",
841                 "0.001:0.005:0.01:0.02:0.05:0.1:0.5",
842                 "0.01:0.05:0.1:0.5:0.9:0.95:0.99"})
843         private String quantiles;
844 
845         /** Data. */
846         private double[] data;
847 
848         /**
849          * @return the data
850          */
851         public double[] getData() {
852             return data;
853         }
854 
855         /**
856          * Create the data.
857          */
858         @Setup
859         public void setup() {
860             data = Arrays.stream(quantiles.split(":")).mapToDouble(Double::parseDouble).toArray();
861         }
862     }
863 
864     /**
865      * Source of quantiles uniformly spaced within a range.
866      */
867     @State(Scope.Benchmark)
868     public static class QuantileRangeSource {
869         /** Lower quantile. */
870         @Param({"0.01"})
871         private double lowerQ;
872         /** Upper quantile. */
873         @Param({"0.99"})
874         private double upperQ;
875         /** Number of quantiles. */
876         @Param({"100"})
877         private int quantiles;
878 
879         /** Data. */
880         private double[] data;
881 
882         /**
883          * @return the data
884          */
885         public double[] getData() {
886             return data;
887         }
888 
889         /**
890          * Create the data.
891          */
892         @Setup
893         public void setup() {
894             if (quantiles < 2) {
895                 throw new IllegalStateException("Bad quantile count: " + quantiles);
896             }
897             if (!(lowerQ >= 0 && upperQ <= 1)) {
898                 throw new IllegalStateException("Bad quantile range: [" + lowerQ + ", " + upperQ + "]");
899             }
900             data = new double[quantiles];
901             for (int i = 0; i < quantiles; i++) {
902                 // Create u in [0, 1]
903                 final double u = i / (quantiles - 1.0);
904                 data[i] = (1 - u) * lowerQ + u * upperQ;
905             }
906         }
907     }
908 
909     /**
910      * Source of a {@link BinaryOperator} for a {@code double[]} and quantiles.
911      */
912     @State(Scope.Benchmark)
913     public static class DoubleQuantileFunctionSource {
914         /** Name of the source. */
915         @Param({JDK, CM3, CM4, STATISTICS})
916         private String name;
917 
918         /** The action. */
919         private BinaryOperator<double[]> function;
920 
921         /**
922          * @return the function
923          */
924         public BinaryOperator<double[]> getFunction() {
925             return function;
926         }
927 
928         /**
929          * Create the function.
930          */
931         @Setup
932         public void setup() {
933             // Note: Functions should not defensively copy the data
934             // as a clone is passed in from the data source.
935             if (JDK.equals(name)) {
936                 function = DoubleQuantileFunctionSource::sortQuantile;
937             } else if (CM3.equals(name)) {
938                 // No way to avoid a data copy here. CM does
939                 // defensive copying for most array input.
940                 final org.apache.commons.math3.stat.descriptive.rank.Percentile s =
941                     new org.apache.commons.math3.stat.descriptive.rank.Percentile().withNaNStrategy(
942                         org.apache.commons.math3.stat.ranking.NaNStrategy.FIXED);
943                 function = (x, p) -> {
944                     final double[] q = new double[p.length];
945                     s.setData(x);
946                     for (int i = 0; i < p.length; i++) {
947                         // Convert quantile to percentile
948                         q[i] = s.evaluate(p[i] * 100);
949                     }
950                     return q;
951                 };
952             } else if (CM4.equals(name)) {
953                 // CM4 differs from CM3 by using Double.compare(x, y) for comparisons.
954                 // This handles NaN and signed zeros but is slower than using < and >.
955                 final org.apache.commons.math4.legacy.stat.descriptive.rank.Percentile s =
956                     new org.apache.commons.math4.legacy.stat.descriptive.rank.Percentile().withNaNStrategy(
957                         org.apache.commons.math4.legacy.stat.ranking.NaNStrategy.FIXED);
958                 function = (x, p) -> {
959                     final double[] q = new double[p.length];
960                     s.setData(x);
961                     for (int i = 0; i < p.length; i++) {
962                         // Convert quantile to percentile
963                         q[i] = s.evaluate(p[i] * 100);
964                     }
965                     return q;
966                 };
967             } else if (STATISTICS.equals(name)) {
968                 function = Quantile.withDefaults()::evaluate;
969             } else {
970                 throw new IllegalStateException("Unknown double[] function: " + name);
971             }
972         }
973 
974         /**
975          * Sort the values and compute the median.
976          *
977          * @param values Values.
978          * @param p p-th quantiles to compute.
979          * @return the quantiles
980          */
981         private static double[] sortQuantile(double[] values, double[] p) {
982             // Implicit NPE
983             final int n = values.length;
984             if (p.length == 0) {
985                 throw new IllegalArgumentException("No quantiles specified for double[] data");
986             }
987             for (final double pp : p) {
988                 checkQuantile(pp);
989             }
990             // Special cases
991             final double[] q = new double[p.length];
992             if (n <= 1) {
993                 Arrays.fill(q, n == 0 ? Double.NaN : values[0]);
994                 return q;
995             }
996             // A sort is required
997             Arrays.sort(values);
998             for (int i = 0; i < p.length; i++) {
999                 // EstimationMethod.HF6 (as per the Apache Commons Math Percentile
1000                 // legacy implementation)
1001                 final double pos = p[i] * (n + 1);
1002                 final double fpos = Math.floor(pos);
1003                 final int j = (int) fpos;
1004                 final double g = pos - fpos;
1005                 if (j < 1) {
1006                     q[i] = values[0];
1007                 } else if (j >= n) {
1008                     q[i] = values[n - 1];
1009                 } else {
1010                     q[i] = (1 - g) * values[j - 1] + g * values[j];
1011                 }
1012             }
1013             return q;
1014         }
1015     }
1016 
1017 
1018     /**
1019      * Source of a {@link BiFunction} for an {@code int[]} and quantiles.
1020      */
1021     @State(Scope.Benchmark)
1022     public static class IntQuantileFunctionSource {
1023         /** Name of the source. */
1024         @Param({JDK, CM3, CM4, STATISTICS})
1025         private String name;
1026 
1027         /** The action. */
1028         private BiFunction<int[], double[], double[]> function;
1029 
1030         /**
1031          * @return the function
1032          */
1033         public BiFunction<int[], double[], double[]> getFunction() {
1034             return function;
1035         }
1036 
1037         /**
1038          * Create the function.
1039          */
1040         @Setup
1041         public void setup() {
1042             // Note: Functions should not defensively copy the data
1043             // as a clone is passed in from the data source.
1044             if (JDK.equals(name)) {
1045                 function = IntQuantileFunctionSource::sortQuantile;
1046             } else if (STATISTICS.equals(name)) {
1047                 function = Quantile.withDefaults()::evaluate;
1048             } else {
1049                 throw new IllegalStateException("Unknown int[] function: " + name);
1050             }
1051         }
1052 
1053         /**
1054          * Sort the values and compute the median.
1055          *
1056          * @param values Values.
1057          * @param p p-th quantiles to compute.
1058          * @return the quantiles
1059          */
1060         private static double[] sortQuantile(int[] values, double[] p) {
1061             // Implicit NPE
1062             final int n = values.length;
1063             if (p.length == 0) {
1064                 throw new IllegalArgumentException("No quantiles specified for int[] data");
1065             }
1066             for (final double pp : p) {
1067                 checkQuantile(pp);
1068             }
1069             // Special cases
1070             final double[] q = new double[p.length];
1071             if (n <= 1) {
1072                 Arrays.fill(q, n == 0 ? Double.NaN : values[0]);
1073                 return q;
1074             }
1075             // A sort is required
1076             Arrays.sort(values);
1077             for (int i = 0; i < p.length; i++) {
1078                 // EstimationMethod.HF6 (as per the Apache Commons Math Percentile
1079                 // legacy implementation)
1080                 final double pos = p[i] * (n + 1);
1081                 final double fpos = Math.floor(pos);
1082                 final int j = (int) fpos;
1083                 final double g = pos - fpos;
1084                 if (j < 1) {
1085                     q[i] = values[0];
1086                 } else if (j >= n) {
1087                     q[i] = values[n - 1];
1088                 } else {
1089                     q[i] = (1 - g) * values[j - 1] + g * values[j];
1090                 }
1091             }
1092             return q;
1093         }
1094     }
1095 
1096     /**
1097      * Check the quantile {@code p} is in the range {@code [0, 1]}.
1098      *
1099      * @param p Quantile.
1100      * @throws IllegalArgumentException if the quantile is not in the range {@code [0, 1]}
1101      */
1102     private static void checkQuantile(double p) {
1103         if (!(p >= 0 && p <= 1)) {
1104             throw new IllegalArgumentException("Invalid quantile: " + p);
1105         }
1106     }
1107 
1108     /**
1109      * Create the statistic using an array and given quantiles.
1110      *
1111      * @param function Source of the function.
1112      * @param source Source of the data.
1113      * @param quantiles Source of the quantiles.
1114      * @param bh Data sink.
1115      */
1116     @Benchmark
1117     public void doubleQuantiles(DoubleQuantileFunctionSource function, DataSource source,
1118             QuantileSource quantiles, Blackhole bh) {
1119         final int size = source.size();
1120         final double[] p = quantiles.getData();
1121         final BinaryOperator<double[]> fun = function.getFunction();
1122         for (int j = -1; ++j < size;) {
1123             bh.consume(fun.apply(source.getData(j), p));
1124         }
1125     }
1126 
1127     /**
1128      * Create the statistic using an array and given quantiles.
1129      *
1130      * @param function Source of the function.
1131      * @param source Source of the data.
1132      * @param quantiles Source of the quantiles.
1133      * @param bh Data sink.
1134      */
1135     @Benchmark
1136     public void doubleQuantileRange(DoubleQuantileFunctionSource function, DataSource source,
1137             QuantileRangeSource quantiles, Blackhole bh) {
1138         final int size = source.size();
1139         final double[] p = quantiles.getData();
1140         final BinaryOperator<double[]> fun = function.getFunction();
1141         for (int j = -1; ++j < size;) {
1142             bh.consume(fun.apply(source.getData(j), p));
1143         }
1144     }
1145 
1146     /**
1147      * Create the statistic using an array and given quantiles.
1148      *
1149      * @param function Source of the function.
1150      * @param source Source of the data.
1151      * @param quantiles Source of the quantiles.
1152      * @param bh Data sink.
1153      */
1154     @Benchmark
1155     public void intQuantiles(IntQuantileFunctionSource function, DataSource source,
1156             QuantileSource quantiles, Blackhole bh) {
1157         final int size = source.size();
1158         final double[] p = quantiles.getData();
1159         final BiFunction<int[], double[], double[]> fun = function.getFunction();
1160         for (int j = -1; ++j < size;) {
1161             bh.consume(fun.apply(source.getIntData(j), p));
1162         }
1163     }
1164 
1165     /**
1166      * Create the statistic using an array and given quantiles.
1167      *
1168      * @param function Source of the function.
1169      * @param source Source of the data.
1170      * @param quantiles Source of the quantiles.
1171      * @param bh Data sink.
1172      */
1173     @Benchmark
1174     public void intQuantileRange(IntQuantileFunctionSource function, DataSource source,
1175             QuantileRangeSource quantiles, Blackhole bh) {
1176         final int size = source.size();
1177         final double[] p = quantiles.getData();
1178         final BiFunction<int[], double[], double[]> fun = function.getFunction();
1179         for (int j = -1; ++j < size;) {
1180             bh.consume(fun.apply(source.getIntData(j), p));
1181         }
1182     }
1183 }