并行排序算法

Posted

技术标签:

【中文标题】并行排序算法【英文标题】:Parallel Sort Algorithm 【发布时间】:2010-12-26 05:30:44 【问题描述】:

我正在寻找 C# 中并行化(多线程)排序算法的简单实现,它可以在 List<T> 或数组上运行,并且可能使用并行扩展,但那部分并不是绝对必要的。

编辑:Frank Krueger 提供了一个很好的答案,但是我希望将该示例转换为不使用 LINQ 的示例。另请注意,Parallel.Do() 似乎已被 Parallel.Invoke() 取代。

谢谢。

【问题讨论】:

如果排序应该是稳定的(相等的元素保持原来的顺序),或者如果比较昂贵(比较少),mergesort 算法是快速排序的一个很好的替代方案。 【参考方案1】:

从他的文章Parallel Extensions to the .Net Framework 中的“The Darkside”中,我们有这个快速排序的并行扩展版本:

(编辑:由于链接现已失效,感兴趣的读者可以在the Wayback Machine找到它的存档)

private void QuicksortSequential<T>(T[] arr, int left, int right) 
where T : IComparable<T>

    if (right > left)
    
        int pivot = Partition(arr, left, right);
        QuicksortSequential(arr, left, pivot - 1);
        QuicksortSequential(arr, pivot + 1, right);
    


private void QuicksortParallelOptimised<T>(T[] arr, int left, int right) 
where T : IComparable<T>

    const int SEQUENTIAL_THRESHOLD = 2048;
    if (right > left)
    
        if (right - left < SEQUENTIAL_THRESHOLD)
        

            QuicksortSequential(arr, left, right);
        
        else
        
            int pivot = Partition(arr, left, right);
            Parallel.Do(
                () => QuicksortParallelOptimised(arr, left, pivot - 1),
                () => QuicksortParallelOptimised(arr, pivot + 1, right));
        
    

请注意,一旦项目数少于 2048,他就会恢复为顺序排序。

【讨论】:

谢谢你,Richard - 我以为我必须回答一个 VB 问题或其他问题才能获得 10,000 :-) 用别人的代码打破障碍也感觉很蹩脚,但我会接受它。 我不确定 Parallel.Do 的语义,但我预计这对于大型数组会表现不佳,因为可能会为每个 达到 10,010。谢谢,这个完整而快速的答案实际上非常有用。你可以放心,你在一个“正确”的问题上得到了 10K ;) @KernelJ 您的担忧是完全有道理的,但我选择了这个实现,因为作者实际上很费心为他的结果计时,而且这个确实比顺序版本的性能更好。还要记住,并行扩展不只是随意创建线程,线程是池化的,它很聪明,不会创建超出 CPU 处理能力的线程。 ps。 Parallel.Do 现在是 Parallel.Invoke,如果数组足够大,你可以期望获得巨大的收益(50%!)。【参考方案2】:

更新我现在在双核机器上实现了 1.7 倍以上的加速。

我想我会尝试编写一个在 .NET 2.0 中工作的并行排序器(我认为,请检查我)并且除了 ThreadPool 之外不使用任何其他东西。

以下是对 2,000,000 元素数组进行排序的结果:

时间并行时间序列 ------------- --------------- 2854 毫秒 5052 毫秒 2846 毫秒 4947 毫秒 2794 毫秒 4940 毫秒 …… 2815 毫秒 4894 毫秒 2981 毫秒 4991 毫秒 2832 毫秒 5053 毫秒 平均:2818 毫秒 平均:4969 毫秒 标准:66 毫秒标准:65 毫秒 速度:1.76x

在这种环境下,我获得了 1.76 倍的加速 - 非常接近我希望的最佳 2 倍:

    2,000,000 个随机 Model 对象 通过比较两个 DateTime 属性的比较委托对对象进行排序。 Mono JIT 编译器版本 2.4.2.3 2.4 GHz Intel Core 2 Duo 上的 Max OS X 10.5.8

这次我使用了Ben Watson's QuickSort in C#。我将他的QuickSort 内部循环从:

QuickSortSequential:
    QuickSortSequential (beg, l - 1);
    QuickSortSequential (l + 1, end);

到:

QuickSortParallel:
    ManualResetEvent fin2 = new ManualResetEvent (false);
    ThreadPool.QueueUserWorkItem (delegate 
        QuickSortParallel (l + 1, end);
        fin2.Set ();
    );
    QuickSortParallel (beg, l - 1);
    fin2.WaitOne (1000000);
    fin2.Close ();

(实际上,在代码中我做了一些负载平衡,似乎确实有帮助。)

我发现,只有当数组中有超过 25,000 个项目时,运行这个并行版本才会有回报(不过,至少 50,000 个似乎让我的处理器喘不过气来)。

我已经在我的小型双核机器上进行了尽可能多的改进。我很想尝试一些关于 8 路怪物的想法。此外,这项工作是在一台运行 Mono 的 13 英寸 MacBook 上完成的。我很好奇其他人在正常的 .NET 2.0 安装上的表现如何。

所有丑陋的源代码都可以在这里找到:http://www.praeclarum.org/so/psort.cs.txt。有兴趣我可以清理一下。

【讨论】:

我有兴趣,但是上面的文章链接和源代码链接都坏了。 你应该合并你的答案,【参考方案3】:

这里记录的是一个没有 lamda 表达式的版本,它将在 C#2 和 .Net 2 + Parallel Extensions 中编译。这也应该适用于 Mono 及其自己的并行扩展实现(来自 Google Summer of code 2008):

/// <summary>
/// Parallel quicksort algorithm.
/// </summary>
public class ParallelSort

    #region Public Static Methods

    /// <summary>
    /// Sequential quicksort.
    /// </summary>
    /// <typeparam name="T"></typeparam>
    /// <param name="arr"></param>
    public static void QuicksortSequential<T>(T [] arr) where T : IComparable<T>
    
        QuicksortSequential(arr, 0, arr.Length - 1);
    

    /// <summary>
    /// Parallel quicksort
    /// </summary>
    /// <typeparam name="T"></typeparam>
    /// <param name="arr"></param>
    public static void QuicksortParallel<T>(T[] arr) where T : IComparable<T>
    
        QuicksortParallel(arr, 0, arr.Length - 1);
    

    #endregion

    #region Private Static Methods

    private static void QuicksortSequential<T>(T[] arr, int left, int right) 
        where T : IComparable<T>
    
        if (right > left)
        
            int pivot = Partition(arr, left, right);
            QuicksortSequential(arr, left, pivot - 1);
            QuicksortSequential(arr, pivot + 1, right);
        
    

    private static void QuicksortParallel<T>(T[] arr, int left, int right) 
        where T : IComparable<T>
    
        const int SEQUENTIAL_THRESHOLD = 2048;
        if (right > left)
        
            if (right - left < SEQUENTIAL_THRESHOLD)
            
                QuicksortSequential(arr, left, right);
            
            else
            
                int pivot = Partition(arr, left, right);
                Parallel.Invoke(new Action[]  delegate QuicksortParallel(arr, left, pivot - 1);,
                                               delegate QuicksortParallel(arr, pivot + 1, right);
                );
            
        
    

    private static void Swap<T>(T[] arr, int i, int j)
    
        T tmp = arr[i];
        arr[i] = arr[j];
        arr[j] = tmp;
    

    private static int Partition<T>(T[] arr, int low, int high) 
        where T : IComparable<T>
    
        // Simple partitioning implementation
        int pivotPos = (high + low) / 2;
        T pivot = arr[pivotPos];
        Swap(arr, low, pivotPos);

        int left = low;
        for (int i = low + 1; i <= high; i++)
        
            if (arr[i].CompareTo(pivot) < 0)
            
                left++;
                Swap(arr, i, left);
            
        

        Swap(arr, low, left);
        return left;
    

    #endregion

【讨论】:

不幸的是,这个分区在数据很容易排序时非常慢。例如,当在零数组上调用时。 int[] arr = new int[1024*1024*128]; ParallelSort.QuicksortParallel(arr); 然后分区将是 [1,2,3,... array.Length]。它似乎无效。【参考方案4】:

我想到了基于处理器缓存大小的合并排序,块在处理器之间划分。

合并阶段可以通过将合并拆分为 n 位来完成,每个处理器开始将块从正确的偏移量合并到块中。

我没试过写这个。

(由于CPU缓存和主内存的相对速度,与发现合并排序时RAM和磁带的相对速度相差不远,也许我们应该重新开始使用合并排序)

【讨论】:

【参考方案5】:

根据您拥有的处理器数量,将您需要排序的列表划分为大小相等的子列表,然后每当完成两个部分时,将它们合并到一个新部分,直到只剩下一个并且所有线程都完成。非常简单,您应该能够自己实现它,并且可以使用任何现有的排序算法(如库中的)对每个线程中的列表进行排序。

【讨论】:

以上是关于并行排序算法的主要内容,如果未能解决你的问题,请参考以下文章

快速排序到底有多快?(含代码分析9大排序算法并行运行对比视频)

基于学习排序的并行协同过滤推荐算法

递归深度截止策略:并行快速排序

三十分钟理解:双调排序Bitonic Sort,适合并行计算的排序算法

奇偶排序

并行计算基础