Java 为什么我的多线程排序算法不如单线程合并排序算法快

Java 为什么我的多线程排序算法不如单线程合并排序算法快,java,multithreading,parallel-processing,Java,Multithreading,Parallel Processing,有一些算法,当一个人把一个任务分成几个部分并把每个部分并行完成时,它的运行时间会大大减少。其中一种算法是合并排序,将列表分成无限小的部分,然后按排序顺序重新组合。我决定做一个实验来测试我是否可以通过使用多个线程来提高这种速度。我使用Java在装有Windows Vista的四核Dell上运行以下功能 一个函数(控制案例)是简单的递归函数: // x is an array of N elements in random order public int[] mergeSort(int[] x)

有一些算法,当一个人把一个任务分成几个部分并把每个部分并行完成时,它的运行时间会大大减少。其中一种算法是合并排序,将列表分成无限小的部分,然后按排序顺序重新组合。我决定做一个实验来测试我是否可以通过使用多个线程来提高这种速度。我使用Java在装有Windows Vista的四核Dell上运行以下功能

一个函数(控制案例)是简单的递归函数:

// x is an array of N elements in random order
public int[] mergeSort(int[] x) {
    if (x.length == 1) 
        return x;

    // Dividing the array in half
    int[] a = new int[x.length/2];
    int[] b = new int[x.length/2+((x.length%2 == 1)?1:0)];
    for(int i = 0; i < x.length/2; i++) 
        a[i] = x[i];
    for(int i = 0; i < x.length/2+((x.length%2 == 1)?1:0); i++) 
        b[i] = x[i+x.length/2];

    // Sending them off to continue being divided
    mergeSort(a);
    mergeSort(b);

    // Recombining the two arrays
    int ia = 0, ib = 0, i = 0;
    while(ia != a.length || ib != b.length) {
        if (ia == a.length) {
            x[i] = b[ib];
            ib++;
        }
        else if (ib == b.length) {
            x[i] = a[ia];
            ia++;
        }
        else if (a[ia] < b[ib]) {
            x[i] = a[ia];
            ia++;
        }
        else {
            x[i] = b[ib];
            ib++;
        }
        i++;
    }

    return x;
}
//x是一个由N个元素按随机顺序排列的数组
公共int[]合并排序(int[]x){
如果(x.length==1)
返回x;
//将数组一分为二
int[]a=新的int[x.length/2];
int[]b=新的int[x.length/2+((x.length%2==1)?1:0)];
对于(int i=0;i
另一个是在扩展线程的类的“run”函数中,每次调用时递归创建两个新线程:

public class Merger extends Thread
{
    int[] x;
    boolean finished;

    public Merger(int[] x)
    {
        this.x = x;
    }

    public void run()
    {
        if (x.length == 1) {
            finished = true;
            return;
        }

        // Divide the array in half
        int[] a = new int[x.length/2];
        int[] b = new int[x.length/2+((x.length%2 == 1)?1:0)];
        for(int i = 0; i < x.length/2; i++) 
            a[i] = x[i];
        for(int i = 0; i < x.length/2+((x.length%2 == 1)?1:0); i++) 
            b[i] = x[i+x.length/2];

        // Begin two threads to continue to divide the array
        Merger ma = new Merger(a);
        ma.run();
        Merger mb = new Merger(b);
        mb.run();

        // Wait for the two other threads to finish 
        while(!ma.finished || !mb.finished) ;

        // Recombine the two arrays
        int ia = 0, ib = 0, i = 0;
        while(ia != a.length || ib != b.length) {
            if (ia == a.length) {
                x[i] = b[ib];
                ib++;
            }
            else if (ib == b.length) {
                x[i] = a[ia];
                ia++;
            }
            else if (a[ia] < b[ib]) {
                x[i] = a[ia];
                ia++;
            }
            else {
                x[i] = b[ib];
                ib++;
            }
            i++;
        }

        finished = true;
    }
}
公共类合并扩展线程
{
int[]x;
布尔完成;
公开合并(int[]x)
{
这个.x=x;
}
公开募捐
{
如果(x.length==1){
完成=正确;
返回;
}
//将数组一分为二
int[]a=新的int[x.length/2];
int[]b=新的int[x.length/2+((x.length%2==1)?1:0)];
对于(int i=0;i

事实证明,不使用多线程的函数实际上运行得更快。为什么?操作系统和java虚拟机是否没有足够有效地“通信”,将不同的线程放在不同的内核上?还是我遗漏了一些明显的东西?

同步的开销可能相对较大,并阻止了许多优化

此外,您创建的线程太多了

另一个是在一个类的“run”函数中,该类扩展thread,并在每次调用它时递归地创建两个新线程

您最好使用固定数量的线程,建议在四核上使用4个线程。这可以通过线程池()实现,模式是“任务包”。但是,也许最好先将任务划分为四个同样大的任务,并对这些任务进行“单线程”排序。这样可以更好地利用缓存



与其让“忙循环”等待线程完成(窃取cpu周期),不如看看
Thread.join()

数组中有多少元素需要排序?如果元素太少,则同步和CPU切换的时间将随着您为并行划分作业而节省的时间而变化

问题不在于多线程:我用Java编写了一个正确的多线程快速排序,它拥有默认的Java排序。我是在目睹了一个巨大的数据集正在处理并且只有一台16核机器的一个核在工作之后这样做的

您的一个问题(一个巨大的问题)是您正忙于循环:

 // Wait for the two other threads to finish 
 while(!ma.finished || !mb.finished) ;
这是一个巨大的否定:它被称为忙循环,你正在破坏性能

(另一个问题是,您的代码没有生成任何新线程,正如已经向您指出的那样)

您需要使用其他方式进行同步:例如使用
倒计时闩锁

另一件事:在分配工作负载时,不需要生成两个新线程:只生成一个新线程,在当前线程中执行另一半

此外,您可能不希望创建的线程数超过可用的内核数

请看我的问题(询问一个好的开源多线程mergesort/quicksort/which)。我用的是专有的,我不能粘贴

我没有实现Mergesort,但实现了QuickSort,我可以告诉您,没有进行数组复制

我所做的是:

  • 选择一个支点
  • 根据需要交换值
  • 我们达到线程限制了吗?(取决于芯数)
    • 是:排序此线程中的第一部分
    • 否:生成一个新线程
  • 对当前线程中的第二部分进行排序
  • 如果需要,请等待第一部分完成
                final CountDownLatch cdl = new CountDownLatch( 1 );
                final Thread t = new Thread( new Runnable() {
                    public void run() {
                        quicksort(a, i+1, r );
                        cdl.countDown();
                    }
                } };
    
    if ( threads.getAndIncrement() < 4 ) {
        final CountDownLatch innerLatch = new CountDownLatch( 1 );
        final Thread t = new Merger( innerLatch, b );
        t.start();
        mergeSort( a );
        while ( innerLatch.getCount() > 0 ) {
            try {
                innerLatch.await( 1000, TimeUnit.SECONDS );
            } catch ( InterruptedException e ) {
                // Up to you to decide what to do here
            }
        }
    } else {
        mergeSort( a );
        mergeSort( b );
    }
    
    Runtime.getRuntime().availableProcessors()