# HG changeset patch # User rbackman # Date 1302606845 -7200 # Node ID c7df2225fecc3561862213c8b9c345942a00d10e # Parent 142881732d3cc25703cbb35ee5f48023585cfad1 7026304: Fork-Join sample Summary: Implement a merge-sort sample using Fork-Join Reviewed-by: hosterda, chegar, dholmes diff -r 142881732d3c -r c7df2225fecc jdk/make/mksample/Makefile --- a/jdk/make/mksample/Makefile Tue Apr 12 09:04:57 2011 +0200 +++ b/jdk/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 diff -r 142881732d3c -r c7df2225fecc jdk/make/mksample/forkjoin/Makefile --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/jdk/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 diff -r 142881732d3c -r c7df2225fecc jdk/make/mksample/forkjoin/mergesort/Makefile --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/jdk/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 diff -r 142881732d3c -r c7df2225fecc jdk/src/share/sample/forkjoin/mergesort/MergeDemo.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/jdk/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 " + + " "); + 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 iterations number of test sorts of a random array of element 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); + } +} diff -r 142881732d3c -r c7df2225fecc jdk/src/share/sample/forkjoin/mergesort/MergeSort.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/jdk/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 job = pool.submit(new MergeSortTask(array, 0, array.length)); + job.join(); + } +} diff -r 142881732d3c -r c7df2225fecc jdk/test/sample/mergesort/MergeSortTest.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/jdk/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!"); + } + } + + +}