【问题标题】:Merge sort with goroutines vs normal Mergesort使用 goroutine 进行合并排序与普通合并排序
【发布时间】:2014-04-03 22:29:03
【问题描述】:

我在 Go 中编写了两个版本的归并排序。一个有 goroutine,另一个没有。我正在比较每个人的表现,我一直在看

https://github.com/denniss/goplayground/blob/master/src/example/sort.go#L69

那是使用 goroutines 的那个。这是一个没有

https://github.com/denniss/goplayground/blob/master/src/example/sort.go#L8

我一直试图弄清楚为什么 goroutine 实现的性能比没有的要差得多。这是我在本地看到的号码

 go run src/main.go
[5 4 3 2 1]
Normal Mergesort
[1 2 3 4 5]
Time:  724
Mergesort with Goroutines
[1 2 3 4 5]
Time:  26690

但我仍然无法弄清楚原因。想知道你们是否可以给我关于做什么/看什么的建议或想法。在我看来,使用 goroutines 的实现至少应该表现得更好一些。我这么说主要是因为以下几行

go MergeSortAsync(numbers[0:m], lchan)
go MergeSortAsync(numbers[m:l], rchan)

【问题讨论】:

  • 由于您的工作受 CPU 限制,您很可能看不到性能提升,因为 Go 默认只使用 1 个线程。查看 GOMAXPROCS 环境变量,尝试将其设置为 2,然后看看会发生什么。
  • 对 5 个项目进行排序不是一个现实的测试。
  • 感谢大家的回复。尝试将 GOMAXPROCS 设置为 2 runtime.GOMAXPROCS(2),但仍然不行。是的,我尝试对 100 进行排序,结果几乎相同。使用 goroutine 的表现更差。
  • 您需要 waaaaaaaay 更多的数据才能像@FredtheMagicWonderDog 提到的那样进行适当的测试。当你有足够的数据时,实际工作会变得更加昂贵,因此你现在拥有的东西是有保证的……这只是没有实际数据量的开销。
  • 请:使用包测试的正常基准测试功能,而不是您自己的计时。

标签: sorting go mergesort goroutine


【解决方案1】:

使用并发并不一定会使算法运行得更快。事实上,除非算法本质上是并行的,否则它会减慢执行速度。

一个处理器 (CPU) 一次只能做一件事,即使在我们看来,它似乎同时做两件事。两个 goroutine 的指令可能是交错的,但这并不会使它们比单个 goroutine 运行得更快。在任何给定时刻,只执行来自其中一个 goroutine 的一条指令(这有一些非常低级的例外,具体取决于硬件功能)——除非您的程序在多个内核上运行。

据我所知,标准的归并排序算法本质上并不是并行的; some modifications need to be made 优化它以在多个处理器上并行执行。即使您使用多个处理器,算法也需要针对它进行优化。

这些优化通常与渠道的使用有关。我不同意“写入通道有很大的开销”本身(Go 使其非常高效),但是,它确实引入了 goroutine 阻塞的可能性。显着减慢程序速度的不是对通道的实际写入,而是调度/同步:等待和唤醒任何一个 goroutine 写入或读取通道可能是瓶颈。

为了补充 Not_a_Golfer 的回答,我同意 goroutine 在执行 I/O 操作时肯定会发光——即使是在单个内核上——因为这些操作发生在远离 CPU 的地方。当一个 goroutine 等待 I/O 时,调度程序可以调度另一个 CPU-bound goroutine 来运行。然而,当部署在多个处理器/内核上时,goroutines 也适用于 CPU 密集型操作。

【讨论】:

    【解决方案2】:

    正如其他人所解释的,并行性是有代价的。您需要看到足够的收益来补偿该成本。这只发生在工作单元大于使通道和 goroutine 接收结果的成本时。

    您可以通过试验来确定工作单​​元应该是什么。假设工作单元是对 1000 个元素进行排序。在这种情况下,您可以像这样轻松更改代码:

    func MergeSortAsync(numbers [] int, resultChan chan []int)  {
        l := len(numbers)
        if l <= 1000 {
                resultChan <- Mergesort(numbers)
                return
        }
    

    换句话说,一旦工作单元太小而无法证明使用 goroutine 和通道的合理性,请使用您的简单 Mergesort 而无需这些成本。

    【讨论】:

      【解决方案3】:

      主要有两个原因:

      1. 写入通道的开销很大。作为参考 - 我尝试使用通道和 goroutines 作为迭代器。它们比重复调用方法慢约 100 倍。当然,如果通过通道传输的操作需要很长时间才能执行(比如抓取网页),那么这种差异可以忽略不计。

      2. Goroutines 在基于 IO 的并发方面确实大放异彩,而在 CPU 并行性方面则不然。

      考虑到这两个问题,您将需要大量 CPU 或更长的 CPU,每个 goroutine 的阻塞操作更少,以加快速度。

      【讨论】:

      • “Goroutines 在基于 IO 的并发方面确实大放异彩,而在 CPU 并行方面则不然”——这对我来说是个新闻,需要详细说明吗?
      猜你喜欢
      • 1970-01-01
      • 2011-04-01
      • 2014-01-14
      • 2022-01-10
      • 2018-10-16
      • 2016-01-06
      • 1970-01-01
      • 2017-07-05
      • 2011-09-01
      相关资源
      最近更新 更多