假设要做这么一件事:给你一个double类型数组,让你求这个数组的元素的倒数和,怎么做?当然是先求倒数,然后再求和啦。没错,就是这样,但是如果你有多个cpu core呢?比如四个core, 线性地这么去求是不是有些浪费计算资源,或者说没有充分利用多核的条件以降低执行时间呢?那可不可以把这这个数组拆分成4段,每个核去计算一段的倒数和,然后等都计算完成了,再把结果相加呢?当然可以!就应该这样做,那java中怎么做呢?用fork/join框架就可以了,使用的方法之一是利用RecursiveAction类,我们只要自己新建一个任务类继承这个类,关键是要重写其compute()方法以实现其计算逻辑就可以了。下面是一个一般化地完成这个任务的例子:
package edu.coursera.parallel; import java.util.concurrent.RecursiveAction; /** * Class wrapping methods for implementing reciprocal array sum in parallel. */ public final class ReciprocalArraySum { /** * Default constructor. */ private ReciprocalArraySum() { } private static int getNCores() { String ncoresStr = System.getenv("COURSERA_GRADER_NCORES"); if (ncoresStr == null) { return Runtime.getRuntime().availableProcessors(); } else { return Integer.parseInt(ncoresStr); } } /** * Sequentially compute the sum of the reciprocal values for a given array. * * @param input Input array * @return The sum of the reciprocals of the array input */ protected static double seqArraySum(final double[] input) { double sum = 0; // Compute sum of reciprocals of array elements for (int i = 0; i < input.length; i++) { sum += 1 / input[i]; } return sum; } /** * Computes the size of each chunk, given the number of chunks to create * across a given number of elements. * * @param nChunks The number of chunks to create * @param nElements The number of elements to chunk across * @return The default chunk size */ private static int getChunkSize(final int nChunks, final int nElements) { // Integer ceil return (nElements + nChunks - 1) / nChunks; } /** * Computes the inclusive element index that the provided chunk starts at, * given there are a certain number of chunks. * * @param chunk The chunk to compute the start of * @param nChunks The number of chunks created * @param nElements The number of elements to chunk across * @return The inclusive index that this chunk starts at in the set of * nElements */ private static int getChunkStartInclusive(final int chunk, final int nChunks, final int nElements) { final int chunkSize = getChunkSize(nChunks, nElements); return chunk * chunkSize; } /** * Computes the exclusive element index that the provided chunk ends at, * given there are a certain number of chunks. * * @param chunk The chunk to compute the end of * @param nChunks The number of chunks created * @param nElements The number of elements to chunk across * @return The exclusive end index for this chunk */ private static int getChunkEndExclusive(final int chunk, final int nChunks, final int nElements) { final int chunkSize = getChunkSize(nChunks, nElements); final int end = (chunk + 1) * chunkSize; if (end > nElements) { return nElements; } else { return end; } } /** * This class stub can be filled in to implement the body of each task * created to perform reciprocal array sum in parallel. */ private static class ReciprocalArraySumTask extends RecursiveAction { /** * Starting index for traversal done by this task. */ private final int startIndexInclusive; /** * Ending index for traversal done by this task. */ private final int endIndexExclusive; /** * Input array to reciprocal sum. */ private final double[] input; /** * Intermediate value produced by this task. */ private double value; /** * Constructor. * @param setStartIndexInclusive Set the starting index to begin * parallel traversal at. * @param setEndIndexExclusive Set ending index for parallel traversal. * @param setInput Input values */ ReciprocalArraySumTask(final int setStartIndexInclusive, final int setEndIndexExclusive, final double[] setInput) { this.startIndexInclusive = setStartIndexInclusive; this.endIndexExclusive = setEndIndexExclusive; this.input = setInput; } /** * Getter for the value produced by this task. * @return Value produced by this task */ public double getValue() { return value; } @Override protected void compute() { // TODO // calculate sequentially value = 0; for (int i = startIndexInclusive; i < endIndexExclusive; i++) { value += 1 / input[i]; } } } /** * TODO: Modify this method to compute the same reciprocal sum as * seqArraySum, but use two tasks running in parallel under the Java Fork * Join framework. You may assume that the length of the input array is * evenly divisible by 2. * * @param input Input array * @return The sum of the reciprocals of the array input */ protected static double parArraySum(final double[] input) { assert input.length % 2 == 0; int mid = input.length / 2; ReciprocalArraySumTask lower = new ReciprocalArraySumTask(0, mid, input); ReciprocalArraySumTask high = new ReciprocalArraySumTask(mid, input.length, input); lower.fork(); high.compute(); lower.join(); return lower.getValue()+high.getValue(); } /** * TODO: Extend the work you did to implement parArraySum to use a set * number of tasks to compute the reciprocal array sum. You may find the * above utilities getChunkStartInclusive and getChunkEndExclusive helpful * in computing the range of element indices that belong to each chunk. * * @param input Input array * @param numTasks The number of tasks to create * @return The sum of the reciprocals of the array input */ protected static double parManyTaskArraySum(final double[] input, final int numTasks) { int taskNum = numTasks; if(taskNum>input.length){ taskNum = input.length; } ReciprocalArraySumTask[] tasks = new ReciprocalArraySumTask[taskNum]; int i = 0; for(i=0; i<taskNum-1; i++){ tasks[i] = new ReciprocalArraySumTask(getChunkStartInclusive(i, taskNum, input.length), getChunkEndExclusive(i, taskNum, input.length), input); tasks[i].fork(); } tasks[i] = new ReciprocalArraySumTask(getChunkStartInclusive(i, taskNum, input.length), getChunkEndExclusive(i, taskNum, input.length), input); tasks[i].compute(); for(int j=0; j<taskNum-1; j++){ tasks[j].join(); } double sum = 0; for(int j=0; j<taskNum; j++){ sum += tasks[j].getValue(); } return sum; } public static void main(String[] args){ double[] arr = {2,4,8,10}; double seq = seqArraySum(arr); double half = parArraySum(arr); double many = parManyTaskArraySum(arr, 4); System.out.println(seq); System.out.println(half); System.out.println(many); } }
时间: 2024-10-31 10:43:18