【问题标题】:Can't get any speedup from parallelizing Quicksort using Pthreads使用 Pthread 并行化快速排序无法获得任何加速
【发布时间】:2010-06-07 13:44:15
【问题描述】:

在列表被分成左右两半(小于和大于枢轴)之后,我正在使用 Pthreads 为每个分区创建一个新的线程。我递归地执行此操作,直到达到允许的最大线程数。

当我使用 printfs 跟踪程序中发生的事情时,我清楚地看到每个线程都在并行执行其委派工作。但是,使用单个进程始终是最快的。一旦我尝试使用更多线程,完成所需的时间几乎翻了一番,并且随着线程数的增加而不断增加。

我可以在运行它的服务器上使用多达 16 个处理器。

算法如下: 通过将元素与枢轴进行比较,将数组拆分为左右。 为左右开始一个新线程,并等待线程重新加入。 如果有更多可用线程,它们可以更递归地创建。 每个线程都等待它的子节点加入。

一切对我来说都有意义,排序工作得非常好,但线程越多,它的速度就越慢。

我尝试为要启动的线程设置每个分区的最小元素数(例如 50000)。

我尝试了一种方法,当一个线程完成后,它允许启动另一个线程,这导致数百个线程自始至终启动和完成。我认为开销太大了。所以我摆脱了它,如果一个线程执行完毕,就不会创建新线程。我得到了更多的加速,但仍然比单个进程慢很多。

我使用的代码如下。

http://pastebin.com/UaGsjcq2

有人知道我做错了什么吗?

【问题讨论】:

  • 尝试将num_processes 设置为2,看看会发生什么。

标签: c pthreads quicksort


【解决方案1】:

启动线程有相当多的开销。您最好创建一个具有一些固定数量的线程的线程池,以及一个线程安全队列来排队等待线程执行的作业。线程等待队列中的一个项目,处理该项目,然后等待另一个项目。如果您想真正正确地做事,这应该是一个优先级队列,其排序基于分区的大小(因此您总是首先对最小的分区进行排序,以帮助防止队列大小过大)。

这至少大大减少了启动线程的开销——但这仍然不能保证您会获得比单线程版本更好的性能。特别是,快速排序在 CPU 本身上涉及的工作量很少,它可能几乎完全受内存带宽的限制。一次处理多个分区可能会损害缓存局部性,以至于在任何情况下都会失去速度。

【讨论】:

  • +1 - 虽然我怀疑缓存位置问题。基本上,只要每个线程都在处理一个实质性的分区,大多数访问都是顺序的,所以坏缓存处理的范围应该很小。也就是说,50,000 个整数有点偏小(很容易放入 CPU 缓存中,太小而无法充分利用缓存的外层),但页面大小可能是真正的问题,但我不知道它有多大这些日子。尽管如此,如果您的内存带宽被充分利用,多线程也无济于事 - 并且可能意味着更多的代码浪费缓存空间。
  • +1 - 这听起来很棒 --- 好像它来自多年的经验。
  • @Jacob:好吧,反正我做这些东西已经很长时间了——我只希望这是多年的经验,而不是一年的经验重复了很多次......
【解决方案2】:

第一个猜测是,创建、销毁,尤其是同步线程会消耗掉你可能会获得的收益,具体取决于你正在排序的元素数量。我实际上猜想弥补开销需要相当长的时间,而且很可能永远不会弥补。

因为你有你的排序方式,你有一个线程在等待另一个等待另一个......你并没有真正得到那么多的并行性。您最好使用更线性的排序,也许像 Radix 之类的东西,它将线程与更多的数据分开。这仍然让一个线程等待其他线程很多,但至少线程同时可以做更多的工作。但是,即使这样,我认为线程也不会有太大帮助。

【讨论】:

    【解决方案3】:

    我只是快速浏览一下您的代码。我得到了一个评论。 你为什么用锁。 如果我明白你在做什么是这样的:

    quickSort(array)
    {
        left, right = partition(array);
        newThread(quickSort(left));
        newThread(quickSort(right));
    }
    

    你不应该需要锁。 通常每个快速排序调用都不会访问数组的其他部分。 所以不涉及共享。

    【讨论】:

    • 也许他正在将索引返回到现有数组,而不是创建新的子数组。
    • 锁用于增加全局变量,用于跟踪使用的线程数。
    • 我敢打赌,当且仅当递归级别不是太深时,最好只在算法中添加一个递归级别参数并创建一个新线程。这样,您就不会通过锁伴随每个递归调用......这在较低级别意味着您的所有线程都在争用对 thread_count 变量的读取访问。我最初会尝试 1 级递归,即使这只产生 2 个线程而不是你的最大值。
    • 为什么有一个全局变量可以做到: quickSort(array, maxThread) { ... newThread(quickSort(left, floor(maxThread / 2)); newThread(quickSort(right, maxThread) - 地板(最大线程/2)))); .. }
    【解决方案4】:

    除非每个线程都在单独的处理器或内核上运行,否则它们不会真正并发运行,并且上下文切换时间会很长。线程数应限制为可用执行单元的数量,即便如此,您也必须相信操作系统会将它们分发到单独的处理器/内核,如果它们也用于其他进程,它可能不会这样做。

    您还应该使用静态线程池,而不是动态创建和销毁线程。创建/销毁线程包括从堆中分配/释放堆栈,这是不确定的并且可能很耗时。

    最后,服务器上的 16 个处理器是真实的还是虚拟机?它们是否专门分配给您的流程?

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2021-03-04
      • 1970-01-01
      • 2013-04-07
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多