This is my first attempt to provide some syntactic sugar for doing mutually independent loop iterations in parallel. Thanks to Java 8 lambdas, I can write the parallel loops in pretty elegant fashion compared to pre-lambda Java's.
Also, I have declared the actual forp as synchronized, which leads to the question will it be sufficient to make sure that two or more threads trying to use forp will get to it in some non-overlapping order?
My code is as follows:
ParallelFor.java:
package net.coderodde.util;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.Callable;
import java.util.concurrent.ForkJoinPool;
import static net.coderodde.util.Utilities.checkNotNull;
import static net.coderodde.util.Utilities.checkRange;
/**
* This class implements the parallel for loop. It is assumed that two distinct
* tasks in the loop are independent, i.e., one task needs no output data from
* another task.
*
* @author Rodion Efremov
* @version I
*/
public class ParallelFor {
/**
* The thread pool doing the actual work.
*/
private static final ForkJoinPool pool;
static {
pool = new ForkJoinPool();
}
/**
* Implements the actual parallel for loop.
*
* @param <I> the type of input data.
* @param <O> the type of output data.
* @param inputList the list of individual arguments to the routine to
* parallelize.
* @param outputList the list of output data, may be <code>null</code>
* whenever no output is needed.
* @param body the actual code. May be specified in the form of a
* lambda expression.
*/
public static synchronized final <I, O>
void forp(final List<I> inputList,
final List<O> outputList,
final ParallelLoopBody<I, O> body) {
// Create the tasks.
final List<MyTask<I, O>> tasks = new ArrayList<>(inputList.size());
if (outputList == null) {
// Tasks with no output.
for (int i = 0; i < inputList.size(); ++i) {
tasks.add(new MyTask<>(body, inputList.get(i)));
}
} else {
outputList.clear();
// Tasks with output.
for (int i = 0; i < inputList.size(); ++i) {
outputList.add(null);
tasks.add(new MyTask<>(body,
inputList.get(i),
outputList,
i));
}
}
pool.invokeAll(tasks);
}
/**
* Implements the actual individual task.
*
* @param <I> the type of input data.
* @param <O> the type of output data.
*/
private static class MyTask<I, O> implements Callable<Object> {
/**
* Contains the actual code for transforming input to output.
*/
private final ParallelLoopBody<I, O> body;
/**
* The input datum.
*/
private final I input;
/**
* The list where the output datum is to be placed.
*/
private final List<O> outputList;
/**
* The index in the output list where the output datum is to be placed.
*/
private final int outputIndex;
/**
* Constructs a new task which requires output.
*
* @param body the loop body implementation.
* @param input the input datum.
* @param outputList output list.
* @param outputIndex index within the output list.
*/
MyTask(final ParallelLoopBody<I, O> body,
final I input,
final List<O> outputList,
final int outputIndex) {
checkNotNull(body, "The parallel loop body is null.");
checkNotNull(outputList, "The output list is null.");
checkRange(outputIndex,
outputList.size(),
"The output index is outside the range " +
"[0, " + outputList.size() + "); index: " +
outputIndex + ".");
this.body = body;
this.outputList = outputList;
this.outputIndex = outputIndex;
this.input = input;
}
/**
* Creates a task that does not produce any output.
*
* @param body the loop body implementation.
* @param input the input datum.
*/
MyTask(final ParallelLoopBody<I, O> body,
final I input) {
checkNotNull(body, "The parallel loop body is null.");
this.body = body;
this.input = input;
this.outputList = null;
this.outputIndex = -1;
}
/**
* Runs this task by transforming input using the loop body to output
* and stores it in the output list (if needed).
*
* @return <code>null</code> as a dummy return value.
*/
@Override
public Object call() throws Exception {
final O output = body.execute(input);
if (outputList != null) {
outputList.set(outputIndex, output);
}
return null;
}
}
}
ParallelLoopBody.java:
package net.coderodde.util;
/**
* This interface defines the API for parallel for loop body implementation.
*
* @author Rodion Efremov
* @version I
*
* @param <I> the type of input data.
* @param <O> the type of output data.
*/
public interface ParallelLoopBody<I, O> {
/**
* Transform the input datum to output datum.
*
* @param input the input datum.
* @return output datum.
*/
public O execute(final I input);
}
Utilities.java:
package net.coderodde.util;
import java.util.List;
/**
* This class contains some utility methods.
*
* @author Rodion Efremov
* @version I
*/
public class Utilities {
/**
* Checks that the input reference is not null.
*
* @param reference the reference to check.
* @param message the error message printed when the test fails.
*/
public static final void checkNotNull(final Object reference,
final String message) {
if (reference == null) {
throw new NullPointerException(message);
}
}
/**
* Checks that the input index is within range <tt>[0, size)</tt>.
*
* @param index the index to check.
* @param size the size of the container indexed.
* @param message the error message printed when the test fails.
*/
public static final void checkRange(final int index,
final int size,
final String message) {
if (index < 0 || index >= size) {
throw new IndexOutOfBoundsException(message);
}
}
/**
* Checks whether the input lists are of same length and have the same
* content according to <code>equals</code>.
*
* @param <E> the list component type.
* @param lists the actual lists.
* @return <code>true</code> if the lists are equal, <code>false</code>
* otherwise.
*/
public static final <E> boolean listsEqual(final List<E>... lists) {
if (lists.length == 0) {
return true;
}
for (int i = 0; i < lists.length - 1; ++i) {
if (lists[i].size() != lists[i + 1].size()) {
return false;
}
}
for (int i = 0; i < lists[0].size(); ++i) {
for (int j = 0; j < lists.length - 1; ++j) {
if (!lists[j].get(i).equals(lists[j + 1].get(i))) {
return false;
}
}
}
return true;
}
}
Demo.java:
package net.coderodde.util;
import java.util.ArrayList;
import java.util.List;
import java.util.Random;
import static net.coderodde.util.ParallelFor.forp;
import static net.coderodde.util.Utilities.listsEqual;
/**
* This class contains the entry point to the demo demonstrating the parallel
* for loop and provides some demo-related utility methods.
*
* @author Rodion Efremov
* @versio I
*/
public class Demo {
private static final int INPUT_LENGTH = 50;
private static final int MIN = 10;
private static final int MAX = 40;
public static void main(final String... args) {
final List<Integer> input = new ArrayList<>();
final List<Long> output = new ArrayList<>();
final long seed = System.currentTimeMillis();
final Random rnd = new Random(seed);
final List<Integer> inputList = getRandomInputList(INPUT_LENGTH,
MIN,
MAX,
rnd);
final List<Long> serialResult = new ArrayList<>();
final List<Long> parallelResult = new ArrayList<>();
System.out.println("Seed: " + seed);
long ta = System.currentTimeMillis();
for (final Integer i : inputList) {
serialResult.add(hardFibonacciWork(i));
}
long tb = System.currentTimeMillis();
System.out.println("Serial time: " + (tb - ta) + " ms.");
ta = System.currentTimeMillis();
//// CHECK THIS OUT: MY FUNKY PARALLEL FOR
forp(inputList,
parallelResult,
(i) -> hardFibonacciWork(i));
//////////////////////////////////
tb = System.currentTimeMillis();
System.out.println("Parallel time: " + (tb - ta) + " ms.");
boolean identical = listsEqual(serialResult, parallelResult);
System.out.println("Output lists identical: " + identical);
if (identical) {
for (int i = 0; i < inputList.size(); ++i) {
System.out.printf("%2d: %-10d %-10d\n", inputList.get(i),
serialResult.get(i),
parallelResult.get(i));
}
}
}
private static long hardFibonacciWork(final int i) {
if (i <= 0) {
return 0L;
}
if (i == 1) {
return 1L;
}
return hardFibonacciWork(i - 1) + hardFibonacciWork(i - 2);
}
private static List<Integer> getRandomInputList(final int size,
final int min,
final int max,
final Random rnd) {
final List<Integer> list = new ArrayList<>(size);
for (int i = 0; i < size; ++i) {
list.add(rnd.nextInt(max - min + 1) + min);
}
return list;
}
}
So, what do you think?