我有合并排序的示例实现,一个使用Fork-Join,另一个是直接递归函数。
看起来fork-join比直接递归慢,为什么?
import java.util.Arrays;
import java.util.List;
import java.util.Random;
import java.util.concurrent.ForkJoinPool;
import java.util.concurrent.RecursiveTask;
class DivideTask extends RecursiveTask<int[]> {
private static final long serialVersionUID = -7017440434091885703L;
int[] arrayToDivide;
public DivideTask(int[] arrayToDivide) {
this.arrayToDivide = arrayToDivide;
}
@Override
protected int[] compute() {
//List<RecursiveTask> forkedTasks = new ArrayList<>();
/*
* We divide the array till it has only 1 element.
* We can also custom define this value to say some
* 5 elements. In which case the return would be
* Arrays.sort(arrayToDivide) instead.
*/
if (arrayToDivide.length > 1) {
List<int[]> partitionedArray = partitionArray();
DivideTask task1 = new DivideTask(partitionedArray.get(0));
DivideTask task2 = new DivideTask(partitionedArray.get(1));
invokeAll(task1, task2);
//Wait for results from both the tasks
int[] array1 = task1.join();
int[] array2 = task2.join();
//Initialize a merged array
int[] mergedArray = new int[array1.length + array2.length];
mergeArrays(task1.join(), task2.join(), mergedArray);
return mergedArray;
}
return arrayToDivide;
}
private void mergeArrays(int[] array1, int[] array2, int[] mergedArray) {
int i = 0, j = 0, k = 0;
while ((i < array1.length) && (j < array2.length)) {
if (array1[i] < array2[j]) {
mergedArray[k] = array1[i++];
} else {
mergedArray[k] = array2[j++];
}
k++;
}
if (i == array1.length) {
for (int a = j; a < array2.length; a++) {
mergedArray[k++] = array2[a];
}
} else {
for (int a = i; a < array1.length; a++) {
mergedArray[k++] = array1[a];
}
}
}
private List<int[]> partitionArray() {
int[] partition1 = Arrays.copyOfRange(arrayToDivide, 0, arrayToDivide.length / 2);
int[] partition2 = Arrays.copyOfRange(arrayToDivide, arrayToDivide.length / 2, arrayToDivide.length);
return Arrays.asList(partition1, partition2);
}
}
public class ForkJoinTest {
static int[] numbers;
static final int SIZE = 1_000_000;
static final int MAX = 20;
public static void main(String[] args) {
setUp();
testMergeSortByFJ();
testMergeSort();
}
static void setUp() {
numbers = new int[SIZE];
Random generator = new Random();
for (int i = 0; i < numbers.length; i++) {
numbers[i] = generator.nextInt(MAX);
}
}
static void testMergeSort() {
long startTime = System.currentTimeMillis();
Mergesort sorter = new Mergesort();
sorter.sort(numbers);
long stopTime = System.currentTimeMillis();
long elapsedTime = stopTime - startTime;
System.out.println("Mergesort Time:" + elapsedTime + " msec");
}
static void testMergeSortByFJ() {
//System.out.println("Unsorted array: " + Arrays.toString(numbers));
long t1 = System.currentTimeMillis();
DivideTask task = new DivideTask(numbers);
ForkJoinPool forkJoinPool = new ForkJoinPool();
forkJoinPool.invoke(task);
//System.out.println("Sorted array: " + Arrays.toString(task.join()));
System.out.println("Fork-Join Time:" + (System.currentTimeMillis() - t1) + " msec");
}
}
class Mergesort {
private int[] msNumbers;
private int[] helper;
private int number;
private void merge(int low, int middle, int high) {
// Copy both parts into the helper array
for (int i = low; i <= high; i++) {
helper[i] = msNumbers[i];
}
int i = low;
int j = middle + 1;
int k = low;
// Copy the smallest values from either the left or the right side back
// to the original array
while (i <= middle && j <= high) {
if (helper[i] <= helper[j]) {
msNumbers[k] = helper[i];
i++;
} else {
msNumbers[k] = helper[j];
j++;
}
k++;
}
// Copy the rest of the left side of the array into the target array
while (i <= middle) {
msNumbers[k] = helper[i];
k++;
i++;
}
}
private void mergesort(int low, int high) {
// Check if low is smaller then high, if not then the array is sorted
if (low < high) {
// Get the index of the element which is in the middle
int middle = low + (high - low) / 2;
// Sort the left side of the array
mergesort(low, middle);
// Sort the right side of the array
mergesort(middle + 1, high);
// Combine them both
merge(low, middle, high);
}
}
public void sort(int[] values) {
this.msNumbers = values;
number = values.length;
this.helper = new int[number];
mergesort(0, number - 1);
}
}
5条答案
按热度按时间j5fpnvbx1#
恕我直言,主要原因不是线程派生和池化带来的开销。
我认为多线程版本运行缓慢的主要原因是您不断地创建新数组,所有时间,数百万次。最终,您创建了具有单个元素的100万个数组,这对于垃圾收集器来说是一个头痛的问题。
您的所有
DivideTask
只能在数组的不同部分(两个部分)上操作,因此只需向它们发送一个范围并使它们在该范围上操作。此外,您的并行化策略使您无法使用聪明的“辅助数组”优化(注意顺序版本中的
helper
数组)。此优化将“输入”数组与合并所基于的“辅助”数组进行交换,因此不应为每个合并操作创建新数组:这是一种节省内存的技术,如果不 * 按递归树的级别 * 进行并行化,就无法实现这种技术。对于一个课堂作业,我不得不并行化MergeSort,我设法通过递归树的级别并行化获得了很好的加速。不幸的是,代码是用C和使用OpenMP。如果你需要,我可以提供它。
kadbb4592#
正如gd 1所指出的,您要做大量的数组分配和复制工作;这会让你付出代价。2你应该在同一个数组的不同部分工作,注意没有子任务在另一个子任务正在工作的部分工作。
但除此之外,fork/join方法(和任何并发方法一样)也会带来一定的开销,事实上,如果你看看RecursiveTask的javadoc,他们甚至指出他们的简单示例执行起来会很慢,因为fork太细了。
长话短说,你应该有更少的细分,每个细分做更多的事情。更一般地说,任何时候你有比核心更多的非阻塞线程,吞吐量不会提高,事实上开销会开始减少。
kjthegm63#
如果不深入研究你的代码,产生一个新线程是很昂贵的。如果你没有太多的工作要做,那么仅仅从性能的Angular 来看是不值得的。这里是非常一般的讨论,但是在一个新线程产生并开始运行之前,一个线程可能会循环数千次(特别是在Windows上)。
请参考Doug Lea's paper(在2.设计下),其中指出:
1aaf6o9v4#
还找到了以下有关使用Fork/Join的信息Dan Grossman的Fork/Join介绍
cl25kdpy5#
我也遇到了同样的问题。在合并排序的实现中,我只复制了右边的部分,它可能比左边的部分短。而且,在复制和合并时,我跳过了右边部分中可能的max元素。即使有了这样的优化,并行实现仍然比迭代实现慢。根据Leetcode,我的迭代方法比91.96%快。我的并行实现快于56.59%。
1.是的。