java - 多线程快速排序比预期慢很多

标签 java multithreading quicksort

基本上,我花了几个小时研究实现递归和多线程快速排序和合并排序的最佳方法(这篇文章仅涉及快速排序)。我的目标是获取给定计算机中的每个逻辑处理器并将它们全部固定以获得最大的快速排序速度。

我在创建线程时采用了递归划分问题的方法,直到数组已排序或达到了 cpu 上的处理器数量,在这种情况下,问题的其余部分不会划分到新线程上,而是在自己的核心上执行其余部分。

在创建了一个只能在我的计算机上运行的非常基本的解决方案之后,我遇到了 Fork/Join 框架,我尝试在下面使用它,但我实际上不知道如何使用。我想出的方法在对 0 - 1000 范围内的 10000000 个随机数进行排序时比单线程对应的要慢,但我仍然认为它很有趣,因为在它的 docs 中。它说,它能够从较慢的线程中窃取工作,无论这意味着什么。

然后我最近听说了线程池,并最初创建了所有线程并将它们分发出去,因为创建新线程会给系统带来负担。但我从未尝试去实现这一点。也许我对 Fork/Join 的理解存在偏差,我想知道是否有人可以指出我正确的方向,或者告诉我在当前程序中我做错了什么。

下面您将看到我对多线程快速排序和单线程快速排序的尝试,这就是我试图将其转换为多线程排序的方法。任何帮助表示赞赏。干杯!.

import java.util.ArrayList;
import java.util.List;
import java.util.Random;
import java.util.concurrent.ForkJoinPool;
import java.util.concurrent.RecursiveAction;


public class MultithreadedQuicksort {
    public static void main(String[] args) {
         List<Comparable> nums = new ArrayList<Comparable>();
         Random rand = new Random();
         for (int i=0; i<10000000; i++) {
            nums.add(rand.nextInt(1000));
         }

         long start = System.currentTimeMillis();
         Quicksort quickSort = new Quicksort(nums, 0, nums.size() -1);
         ForkJoinPool pool = new ForkJoinPool();
         pool.invoke(quickSort);
         long end = System.currentTimeMillis();
         System.out.println(end - start);
         System.out.println(nums.size());
    }
}

class Quicksort extends RecursiveAction {
    int first;
    int last;
    private List<Comparable> nums;
    Comparable midValue;
    int midIndex;
    int low;
    int high;

    public Quicksort(List<Comparable> nums){
        this.nums=nums;
        this.low = 0;
        this.high = nums.size() - 1;
    }

    public Quicksort(List<Comparable> nums, int first, int last) {
        this.first = first;
        this.last = last;
        this.nums = nums;
        this.low = first;
        this.high = last;
        this.midIndex = (first + last) / 2;
        this.midValue = nums.get(midIndex);
    }


    @Override
    protected void compute() {
        split();
        if (high > first)
            invokeAll(new Quicksort(nums, first, high));
        if (low < last)
            invokeAll(new Quicksort(nums, low, last));
    }

    public void split() {
        while(low < high) {
            while (nums.get(low).compareTo(midValue) < 0) {
                  low++;
            }
            while (nums.get(high).compareTo(midValue) > 0) {
                  high--;
            }
            if (low <= high) {
                swap(low, high);
                low++;
                high--;
            }
        }
    }

    public void swap(int index1, int index2)
    {
        Comparable temp;
        temp = nums.get(index1);
        nums.set(index1, nums.get(index2));
        nums.set(index2, temp);
    }
}

单线程

public static void quickSort(List<Comparable> nums, int first, int last) {
    int low = first;
    int high = last;
    int midIndex = (first + last) / 2;
    Comparable midValue = nums.get(midIndex);

    while(low < high) {
        while (nums.get(low).compareTo(midValue) < 0) {
              low++;
        }
        while (nums.get(high).compareTo(midValue) > 0) {
              high--;
        }
        if (low <= high) {
            swap(nums, low, high);
            low++;
            high--;
        }
    }
    if (high > first)
           quickSort(nums, first, high);
    if (low < last)
           quickSort(nums, low, last);
    }

最佳答案

我对java不太了解,所以下面的示例代码可能是多线程中runnable的尴尬用法。此示例代码使用 8 个线程,qsortmt() 进行分区并启动 qsort0() 的两个实例。 qsort0() 的每个实例都会进行分区并调用 qsort1() 的两个实例。 qsort1() 的每个实例都会进行分区并调用 qsort2() 的两个实例。 qsort2() 的每个实例都会调用 qsort()。对于本示例中使用的 1600 万个整数,8 线程排序大约需要 1 秒,而非线程排序大约需要 1.6 秒,因此节省的时间并不多。部分问题是分区步骤是在调用线程对子分区进行操作之前完成的。

切换到C++和Windows native 线程,8个线程大约花费0.632秒,非线程大约1.352秒。切换到合并排序,将数组分成8部分,对每个部分进行排序,然后合并8部分大约需要0.40秒,单线程大约1.45秒。

package x;
import java.util.Random;

public class x {

    class qsort0 implements Runnable
    {
        int[] a;
        int lo;
        int hi;

        private qsort0(int[] a, int lo, int hi)
        {
            this.a = a;
            this.lo = lo;
            this.hi = hi;
        }
        @Override
        public void run()
        {
            if(this.lo >= this.hi)
                return;
            int pi = partition(this.a, this.lo, this.hi);
            Thread lt = new Thread(new qsort1(a, this.lo, pi));
            Thread rt = new Thread(new qsort1(a, pi+1, this.hi));
            lt.start();
            rt.start();
            try {lt.join();} catch (InterruptedException ex){}
            try {rt.join();} catch (InterruptedException ex){}
        }
    }

    class qsort1 implements Runnable
    {
        int[] a;
        int lo;
        int hi;

        private qsort1(int[] a, int lo, int hi)
        {
            this.a = a;
            this.lo = lo;
            this.hi = hi;
        }
        @Override
        public void run()
        {
            if(this.lo >= this.hi)
                return;
            int pi = partition(this.a, this.lo, this.hi);
            Thread lt = new Thread(new qsort2(a, this.lo, pi));
            Thread rt = new Thread(new qsort2(a, pi+1, this.hi));
            lt.start();
            rt.start();
            try {lt.join();} catch (InterruptedException ex){}
            try {rt.join();} catch (InterruptedException ex){}
        }
    }

    class qsort2 implements Runnable
    {
        int[] a;
        int lo;
        int hi;
        private qsort2(int[] a, int lo, int hi)
        {
            this.a = a;
            this.lo = lo;
            this.hi = hi;
        }
        @Override
        public void run() {
            if(this.lo >= this.hi)
                return;
            qsort(this.a, this.lo, this.hi);
        }
    }

    // quicksort multi-threaded
    @SuppressWarnings("empty-statement")
    public static void qsortmt(int[] a, int lo, int hi)
    {
        if(lo >= hi)
            return;
        int pi = partition(a, lo, hi);
        Thread lt = new Thread(new x().new qsort0(a, lo, pi));
        Thread rt = new Thread(new x().new qsort0(a, pi+1, hi));
        lt.start();
        rt.start();
        try {lt.join();} catch (InterruptedException ex){}
        try {rt.join();} catch (InterruptedException ex){}
    }

    @SuppressWarnings("empty-statement")
    public static int partition(int []a, int lo, int hi)
    {
        int  md = lo+(hi-lo)/2;
        int  ll = lo-1;
        int  hh = hi+1;
        int t;
        int p = a[md];
        while(true){
            while(a[++ll] < p);
            while(a[--hh] > p);
            if(ll >= hh)
                break;
            t     = a[ll];
            a[ll] = a[hh];
            a[hh] = t;
        }
        return hh;
    }

    @SuppressWarnings("empty-statement")
    public static void qsort(int[] a, int lo, int hi)
    {
        while(lo < hi){
            int ll = partition(a, lo, hi);
            int hh = ll+1;
            // recurse on smaller part, loop on larger part
            if((ll - lo) <= (hi - hh)){
                qsort(a, lo, ll);
                lo = hh;
            } else {
                qsort(a, hh, hi);
                hi = ll;
            }
        }
    }

    public static void main(String[] args)
    {
        int[] a = new int[16*1024*1024];
        Random r = new Random(0);
        for(int i = 0; i < a.length; i++)
            a[i] = r.nextInt();
        long bgn, end;
        bgn = System.currentTimeMillis();
        qsortmt(a, 0, a.length-1);
        end = System.currentTimeMillis();
        for(int i = 1; i < a.length; i++){
            if(a[i-1] > a[i]){
                System.out.println("failed");
                break;
            }
        }
        System.out.println("milliseconds " + (end-bgn));
    }
}

关于java - 多线程快速排序比预期慢很多,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/59123113/

相关文章:

java - Selenium 工具提示文本验证

java - 为什么这段代码会产生死锁?

performance - 快排递归深度O(n)的栈空间不会造成stackoverflow?

java - String 对象如何工作(如不可变对象(immutable对象))?

java - 文件将多行读取到一个字符串

java - 模板类型转换

Java 迭代器和嵌套哈希表的并发问题

c++ - pthread_mutex_lock 阻塞但 __lock = 0

c - 如何在 ANSI C 中对字符串结构数组使用快速排序

c++ - 当我在快速排序算法的递归调用中包含主元时,为什么会出现堆栈溢出?