changeset 4007:9128eace50f5

7026304: Fork-Join sample Summary: Implement a merge-sort sample using Fork-Join Reviewed-by: hosterda, chegar, dholmes
author rbackman
date Tue, 12 Apr 2011 13:14:05 +0200
parents 54446de9fbb0
children 6e306c3aa17b e142148d8b54
files make/mksample/Makefile make/mksample/forkjoin/Makefile make/mksample/forkjoin/mergesort/Makefile src/share/sample/forkjoin/mergesort/MergeDemo.java src/share/sample/forkjoin/mergesort/MergeSort.java test/sample/mergesort/MergeSortTest.java
diffstat 6 files changed, 611 insertions(+), 2 deletions(-) [+]
line wrap: on
line diff
--- a/make/mksample/Makefile	Tue Apr 12 09:04:57 2011 +0200
+++ b/make/mksample/Makefile	Tue Apr 12 13:14:05 2011 +0200
@@ -1,5 +1,5 @@
 #
-# Copyright (c) 2004, 2010, Oracle and/or its affiliates. All rights reserved.
+# Copyright (c) 2004, 2011, Oracle and/or its affiliates. All rights reserved.
 # DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
 #
 # This code is free software; you can redistribute it and/or modify it
@@ -38,7 +38,7 @@
 endif
 
 SUBDIRS =
-SUBDIRS_misc = nio scripting nbproject
+SUBDIRS_misc = nio scripting nbproject forkjoin
 SUBDIRS_enterprise = $(WEBSERVICES_SUBDIR)
 SUBDIRS_management = jmx
 
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/make/mksample/forkjoin/Makefile	Tue Apr 12 13:14:05 2011 +0200
@@ -0,0 +1,41 @@
+#
+# Copyright (c) 2011, Oracle and/or its affiliates. All rights reserved.
+# DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+#
+# This code is free software; you can redistribute it and/or modify it
+# under the terms of the GNU General Public License version 2 only, as
+# published by the Free Software Foundation.  Oracle designates this
+# particular file as subject to the "Classpath" exception as provided
+# by Oracle in the LICENSE file that accompanied this code.
+#
+# This code is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+# FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+# version 2 for more details (a copy is included in the LICENSE file that
+# accompanied this code).
+#
+# You should have received a copy of the GNU General Public License version
+# 2 along with this work; if not, write to the Free Software Foundation,
+# Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+#
+# Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+# or visit www.oracle.com if you need additional information or have any
+# questions.
+#
+
+#
+# Makefile for building all the samples under the forkjoin subdirectory.
+#
+
+BUILDDIR = ../..
+PRODUCT = java
+include $(BUILDDIR)/common/Defs.gmk
+
+SUBDIRS = mergesort
+include $(BUILDDIR)/common/Subdirs.gmk
+
+all build clean clobber::
+	$(SUBDIRS-loop)
+
+clobber clean ::
+	$(RM) -r $(SAMPLEDIR)/forkjoin
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/make/mksample/forkjoin/mergesort/Makefile	Tue Apr 12 13:14:05 2011 +0200
@@ -0,0 +1,51 @@
+#
+# Copyright (c) 2011, Oracle and/or its affiliates. All rights reserved.
+# DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+#
+# This code is free software; you can redistribute it and/or modify it
+# under the terms of the GNU General Public License version 2 only, as
+# published by the Free Software Foundation.  Oracle designates this
+# particular file as subject to the "Classpath" exception as provided
+# by Oracle in the LICENSE file that accompanied this code.
+#
+# This code is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+# FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+# version 2 for more details (a copy is included in the LICENSE file that
+# accompanied this code).
+#
+# You should have received a copy of the GNU General Public License version
+# 2 along with this work; if not, write to the Free Software Foundation,
+# Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+#
+# Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+# or visit www.oracle.com if you need additional information or have any
+# questions.
+#
+
+#
+# Makefile for the forkjoin/mergesort sample code
+#
+
+BUILDDIR = ../../..
+
+PRODUCT = java
+
+include $(BUILDDIR)/common/Defs.gmk
+
+SAMPLE_SRC_DIR = $(SHARE_SRC)/sample/forkjoin/mergesort
+SAMPLE_DST_DIR = $(SAMPLEDIR)/forkjoin/mergesort
+
+SAMPLE_FILES =							\
+	$(SAMPLE_DST_DIR)/MergeDemo.java			\
+	$(SAMPLE_DST_DIR)/MergeSort.java
+
+all build: $(SAMPLE_FILES)
+
+$(SAMPLE_DST_DIR)/%: $(SAMPLE_SRC_DIR)/%
+	$(install-file)
+
+clean clobber:
+	$(RM) -r $(SAMPLE_DST_DIR)
+
+.PHONY: all build clean clobber
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/share/sample/forkjoin/mergesort/MergeDemo.java	Tue Apr 12 13:14:05 2011 +0200
@@ -0,0 +1,287 @@
+/*
+ * Copyright (c) 2011, Oracle and/or its affiliates. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ *   - Redistributions of source code must retain the above copyright
+ *     notice, this list of conditions and the following disclaimer.
+ *
+ *   - Redistributions in binary form must reproduce the above copyright
+ *     notice, this list of conditions and the following disclaimer in the
+ *     documentation and/or other materials provided with the distribution.
+ *
+ *   - Neither the name of Oracle nor the names of its
+ *     contributors may be used to endorse or promote products derived
+ *     from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
+ * IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+import java.util.Arrays;
+import java.util.Random;
+
+import static java.lang.Integer.parseInt;
+
+/**
+ * MergeExample is a class that runs a demo benchmark of the {@code ForkJoin} framework
+ * by benchmarking a {@link MergeSort} algorithm that is implemented using
+ * {@link java.util.concurrent.RecursiveAction}.
+ * The {@code ForkJoin} framework is setup with different parallelism levels
+ * and the sort is executed with arrays of different sizes to see the
+ * trade offs by using multiple threads for different sizes of the array.
+ */
+public class MergeDemo {
+    // Use a fixed seed to always get the same random values back
+    private final Random random = new Random(759123751834L);
+    private static final int ITERATIONS = 10;
+
+    /**
+     * Represents the formula {@code f(n) = start + (step * n)} for n = 0 & n < iterations
+     */
+    private static class Range {
+        private final int start;
+        private final int step;
+        private final int iterations;
+
+        private Range(int start, int step, int iterations) {
+            this.start = start;
+            this.step = step;
+            this.iterations = iterations;
+        }
+
+        /**
+         * Parses start, step and iterations from args
+         * @param args the string array containing the arguments
+         * @param start which element to start the start argument from
+         * @return the constructed range
+         */
+        public static Range parse(String[] args, int start) {
+            if (args.length < start + 3) {
+                throw new IllegalArgumentException("Too few elements in array");
+            }
+            return new Range(parseInt(args[start]), parseInt(args[start + 1]), parseInt(args[start + 2]));
+        }
+
+        public int get(int iteration) {
+            return start + (step * iteration);
+        }
+
+        public int getIterations() {
+            return iterations;
+        }
+
+        @Override
+        public String toString() {
+            StringBuilder builder = new StringBuilder();
+            builder.append(start).append(" ").append(step).append(" ").append(iterations);
+            return builder.toString();
+        }
+    }
+
+    /**
+     * Wraps the different parameters that is used when running the MergeExample.
+     * {@code sizes} represents the different array sizes
+     * {@code parallelism} represents the different parallelism levels
+     */
+    private static class Configuration {
+        private final Range sizes;
+        private final Range parallelism;
+
+        private final static Configuration defaultConfig = new Configuration(new Range(20000, 20000, 10),
+                new Range(2, 2, 10));
+
+        private Configuration(Range sizes, Range parallelism) {
+            this.sizes = sizes;
+            this.parallelism = parallelism;
+        }
+
+        /**
+         * Parses the arguments and attempts to create a configuration containing the
+         * parameters for creating the array sizes and parallelism sizes
+         * @param args the input arguments
+         * @return the configuration
+         */
+        public static Configuration parse(String[] args) {
+            if (args.length == 0) {
+                return defaultConfig;
+            } else {
+                try {
+                    if (args.length == 6) {
+                        return new Configuration(Range.parse(args, 0), Range.parse(args, 3));
+                    }
+                } catch (NumberFormatException e) {
+                    System.err.println("MergeExample: error: Argument was not a number.");
+                }
+                System.err.println("MergeExample <size start> <size step> <size steps> <parallel start> <parallel step>" +
+                        " <parallel steps>");
+                System.err.println("example: MergeExample 20000 10000 3 1 1 4");
+                System.err.println("example: will run with arrays of sizes 20000, 30000, 40000" +
+                        " and parallelism: 1, 2, 3, 4");
+                return null;
+            }
+        }
+
+        /**
+         * Creates an array for reporting the test result time in
+         * @return an array containing {@code sizes.iterations * parallelism.iterations} elements
+         */
+        private long[][] createTimesArray() {
+            return new long[sizes.getIterations()][parallelism.getIterations()];
+        }
+
+        @Override
+        public String toString() {
+            StringBuilder builder = new StringBuilder("");
+            if (this == defaultConfig) {
+                builder.append("Default configuration. ");
+            }
+            builder.append("Running with parameters: ");
+            builder.append(sizes);
+            builder.append(" ");
+            builder.append(parallelism);
+            return builder.toString();
+        }
+    }
+
+    /**
+     * Generates an array of {@code elements} random elements
+     * @param elements the number of elements requested in the array
+     * @return an array of {@code elements} random elements
+     */
+    private int[] generateArray(int elements) {
+        int[] array = new int[elements];
+        for (int i = 0; i < elements; ++i) {
+            array[i] = random.nextInt();
+        }
+        return array;
+    }
+
+    /**
+     * Runs the test
+     * @param config contains the settings for the test
+     */
+    private void run(Configuration config) {
+        Range sizes = config.sizes;
+        Range parallelism = config.parallelism;
+
+        // Run a couple of sorts to make the JIT compile / optimize the code
+        // which should produce somewhat more fair times
+        warmup();
+
+        long[][] times = config.createTimesArray();
+
+        for (int size = 0; size < sizes.getIterations(); size++) {
+            runForSize(parallelism, sizes.get(size), times, size);
+        }
+
+        printResults(sizes, parallelism, times);
+    }
+
+    /**
+     * Prints the results as a table
+     * @param sizes the different sizes of the arrays
+     * @param parallelism the different parallelism levels used
+     * @param times the median times for the different sizes / parallelism
+     */
+    private void printResults(Range sizes, Range parallelism, long[][] times) {
+        System.out.println("Time in milliseconds. Y-axis: number of elements. X-axis parallelism used.");
+        long[] sums = new long[times[0].length];
+        System.out.format("%8s  ", "");
+        for (int i = 0; i < times[0].length; i++) {
+            System.out.format("%4d ", parallelism.get(i));
+        }
+        System.out.println("");
+        for (int size = 0; size < sizes.getIterations(); size++) {
+            System.out.format("%8d: ", sizes.get(size));
+            for (int i = 0; i < times[size].length; i++) {
+                sums[i] += times[size][i];
+                System.out.format("%4d ", times[size][i]);
+            }
+            System.out.println("");
+        }
+        System.out.format("%8s: ", "Total");
+        for (long sum : sums) {
+            System.out.format("%4d ", sum);
+        }
+        System.out.println("");
+    }
+
+    private void runForSize(Range parallelism, int elements, long[][] times, int size) {
+        for (int step = 0; step < parallelism.getIterations(); step++) {
+            long time = runForParallelism(ITERATIONS, elements, parallelism.get(step));
+            times[size][step] = time;
+        }
+    }
+
+    /**
+     * Runs <i>iterations</i> number of test sorts of a random array of <i>element</i> length
+     * @param iterations number of iterations
+     * @param elements number of elements in the random array
+     * @param parallelism parallelism for the ForkJoin framework
+     * @return the median time of runs
+     */
+    private long runForParallelism(int iterations, int elements, int parallelism) {
+        MergeSort mergeSort = new MergeSort(parallelism);
+        long[] times = new long[iterations];
+
+        for (int i = 0; i < iterations; i++) {
+            // Suggest the VM to run a garbage collection to reduce the risk of getting one
+            // while running the test run
+            System.gc();
+            long start = System.currentTimeMillis();
+            mergeSort.sort(generateArray(elements));
+            times[i] = System.currentTimeMillis() - start;
+        }
+
+        return medianValue(times);
+    }
+
+    /**
+     * Calculates the median value of the array
+     * @param times array of times
+     * @return the median value
+     */
+    private long medianValue(long[] times) {
+        if (times.length == 0) {
+            throw new IllegalArgumentException("Empty array");
+        }
+        // Make a copy of times to avoid having side effects on the parameter value
+        Arrays.sort(times.clone());
+        long median = times[times.length / 2];
+        if (times.length > 1 && times.length % 2 != 0) {
+            median = (median + times[times.length / 2 + 1]) / 2;
+        }
+        return median;
+    }
+
+    /**
+     * Generates 1000 arrays of 1000 elements and sorts them as a warmup
+     */
+    private void warmup() {
+        MergeSort mergeSort = new MergeSort(Runtime.getRuntime().availableProcessors());
+        for (int i = 0; i < 1000; i++) {
+            mergeSort.sort(generateArray(1000));
+        }
+    }
+
+    public static void main(String[] args) {
+        Configuration configuration = Configuration.parse(args);
+        if (configuration == null) {
+            System.exit(1);
+        }
+        System.out.println(configuration);
+        new MergeDemo().run(configuration);
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/share/sample/forkjoin/mergesort/MergeSort.java	Tue Apr 12 13:14:05 2011 +0200
@@ -0,0 +1,128 @@
+/*
+ * Copyright (c) 2011, Oracle and/or its affiliates. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ *   - Redistributions of source code must retain the above copyright
+ *     notice, this list of conditions and the following disclaimer.
+ *
+ *   - Redistributions in binary form must reproduce the above copyright
+ *     notice, this list of conditions and the following disclaimer in the
+ *     documentation and/or other materials provided with the distribution.
+ *
+ *   - Neither the name of Oracle nor the names of its
+ *     contributors may be used to endorse or promote products derived
+ *     from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
+ * IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+import java.util.Arrays;
+import java.util.concurrent.ForkJoinPool;
+import java.util.concurrent.ForkJoinTask;
+import java.util.concurrent.RecursiveAction;
+
+/**
+ * A class for sorting an array of {@code ints} in parallel.
+ * A {@code ForkJoinPool} is used for the parallelism, using the merge sort
+ * algorithm the array is split into halves and a new sub task is created
+ * for each part. Each sub task is dispatched to the {@code ForkJoinPool}
+ * which will schedule the task to a {@code Thread}.
+ * This happens until the size of the array is at most 2
+ * elements long. At this point the array is sorted using a simple compare
+ * and possibly a swap. The tasks then finish by using insert sort to
+ * merge the two just sorted arrays.
+ *
+ * The idea of this class is to demonstrate the usage of RecursiveAction not
+ * to implement the best possible parallel merge sort. This version creates
+ * a small array for each merge (creating a lot of objects), this could
+ * be avoided by keeping a single array.
+ */
+public class MergeSort {
+    private final ForkJoinPool pool;
+
+    private static class MergeSortTask extends RecursiveAction {
+        private final int[] array;
+        private final int low;
+        private final int high;
+        private static final int THRESHOLD = 8;
+
+        /**
+         * Creates a {@code MergeSortTask} containing the array and the bounds of the array
+         *
+         * @param array the array to sort
+         * @param low the lower element to start sorting at
+         * @param high the non-inclusive high element to sort to
+         */
+        protected MergeSortTask(int[] array, int low, int high) {
+            this.array = array;
+            this.low = low;
+            this.high = high;
+        }
+
+        @Override
+        protected void compute() {
+            if (high - low <= THRESHOLD) {
+                Arrays.sort(array, low, high);
+            } else {
+                int middle = low + ((high - low) >> 1);
+                // Execute the sub tasks and wait for them to finish
+                invokeAll(new MergeSortTask(array, low, middle), new MergeSortTask(array, middle, high));
+                // Then merge the results
+                merge(middle);
+            }
+        }
+
+        /**
+         * Merges the two sorted arrays this.low, middle - 1 and middle, this.high - 1
+         * @param middle the index in the array where the second sorted list begins
+         */
+        private void merge(int middle) {
+            if (array[middle - 1] < array[middle]) {
+                return; // the arrays are already correctly sorted, so we can skip the merge
+            }
+            int[] copy = new int[high - low];
+            System.arraycopy(array, low, copy, 0, copy.length);
+            int copyLow = 0;
+            int copyHigh = high - low;
+            int copyMiddle = middle - low;
+
+            for (int i = low, p = copyLow, q = copyMiddle; i < high; i++) {
+                if (q >= copyHigh || (p < copyMiddle && copy[p] < copy[q]) ) {
+                    array[i] = copy[p++];
+                } else {
+                    array[i] = copy[q++];
+                }
+            }
+        }
+    }
+
+    /**
+     * Creates a {@code MergeSort} containing a ForkJoinPool with the indicated parallelism level
+     * @param parallelism the parallelism level used
+     */
+    public MergeSort(int parallelism) {
+        pool = new ForkJoinPool(parallelism);
+    }
+
+    /**
+     * Sorts all the elements of the given array using the ForkJoin framework
+     * @param array the array to sort
+     */
+    public void sort(int[] array) {
+        ForkJoinTask<Void> job = pool.submit(new MergeSortTask(array, 0, array.length));
+        job.join();
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/test/sample/mergesort/MergeSortTest.java	Tue Apr 12 13:14:05 2011 +0200
@@ -0,0 +1,102 @@
+/*
+ * Copyright (c) 2011 Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+
+/* @test
+ * @summary Test MergeSort
+ *
+ * @library ../../../src/share/sample/forkjoin/mergesort
+ * @build MergeSortTest MergeDemo MergeSort
+ * @run main MergeSortTest
+ */
+
+import java.util.Arrays;
+import java.util.Random;
+
+public class MergeSortTest {
+    private Random random;
+    private MergeSort target;
+
+    public MergeSortTest(Random random, MergeSort target) {
+        this.random = random;
+        this.target = target;
+    }
+
+    public static void main(String[] args) {
+        MergeSortTest test = new MergeSortTest(new Random(), new MergeSort(Runtime.getRuntime().availableProcessors() * 4));
+        test.run();
+    }
+
+    private int[] generateArray(int elements) {
+        int[] array = new int[elements];
+        for (int i = 0; i < array.length; ++i) {
+            array[i] = random.nextInt(10);
+        }
+        return array;
+    }
+
+    private void run() {
+        testSort();
+        testSortSingle();
+        testSortEmpty();
+        testLong();
+    }
+
+    public void testLong() {
+        for (int i = 0; i < 1000; ++i) {
+            int elements = 1 + i * 100;
+
+            int[] array = generateArray(elements);
+            int[] copy = Arrays.copyOf(array, array.length);
+            Arrays.sort(copy);
+            target.sort(array);
+            assertEqual(copy, array);
+        }
+   }
+
+    private void testSortEmpty() {
+        int[] array = { };
+        target.sort(array);
+        assertEqual(new int[] { }, array);
+    }
+
+    private void testSortSingle() {
+        int[] array = { 1 };
+        target.sort(array);
+        assertEqual(new int[] { 1 }, array);
+    }
+
+    private void testSort() {
+        int[] array = { 7, 3, 9, 0, -6, 12, 54, 3, -6, 88, 1412};
+        target.sort(array);
+        assertEqual(new int[] { -6, -6, 0, 3, 3, 7, 9, 12, 54, 88, 1412 }, array);
+    }
+
+    private void assertEqual(int[] expected, int[] array) {
+        if (!Arrays.equals(expected, array)) {
+            throw new RuntimeException("Invalid sorted array!");
+        }
+    }
+
+
+}