【Go语言测评】并发编程能快多少?

123 阅读7分钟

开启掘金成长之旅!这是我参与「掘金日新计划 · 2 月更文挑战」的第 18 天,点击查看活动详情

可并行程序

可并行程序是指可以分解成多个子任务,这些子任务可以同时执行的程序。这些子任务可以在多个处理器或计算节点上并行执行,从而大大缩短程序的执行时间。可并行程序的并行化程度可以从完全并行到完全串行不等。

下面是一些可并行程序的例子:

  1. 图像处理程序:图像处理通常可以分解成多个子任务,例如图像分割、滤波和增强等。这些子任务可以在多个处理器上并行执行,从而加快图像处理的速度。
  2. 数值模拟程序:数值模拟通常可以分解成多个子任务,例如离散化、求解线性方程组和解析结果等。这些子任务可以在多个处理器上并行执行,从而加快数值模拟的速度。
  3. 数据库查询程序:数据库查询通常需要对大量数据进行复杂的计算和检索。这些计算可以分解成多个子任务,例如查询优化、索引构建和数据过滤等。这些子任务可以在多个处理器上并行执行,从而加快查询的速度。
  4. 分布式Web爬虫程序:Web爬虫需要同时处理多个URL,并从每个URL中提取信息。这些子任务可以在多个计算节点上并行执行,从而加快Web爬虫的速度。

需要注意的是,并行化程序并不总是可以提高性能。在某些情况下,程序的并行化可能会增加通信和同步的开销,从而降低性能。在并行化程序时,需要根据程序的性质和具体情况来评估程序的可并行性,并确定最佳的并行化策略。

可并行化程序并行加速对比

我们以加法计算为例,重复计算从1加到100w,对比不同cpu个数情况下的平均执行时间:

func job(n int) int {
   tmp := 0
   for i := 0; i < n; i++ {
      tmp += i
   }
   return n
}
func BenchmarkParallelism1(b *testing.B) {
   b.RunParallel(func(pb *testing.PB) {
      for pb.Next() {
         job(1000000)
      }
   })
}

结果如下

goos: windows
goarch: amd64
pkg: learn/basic/prof/parallel/cpu-num
cpu: AMD Ryzen 7 6800H with Radeon Graphics         
BenchmarkParallelism1               5187            227739 ns/op
BenchmarkParallelism1-2            10000            116036 ns/op
BenchmarkParallelism1-3            15052             78976 ns/op
BenchmarkParallelism1-4            20227             60304 ns/op
BenchmarkParallelism1-5            24475             48797 ns/op
BenchmarkParallelism1-6            29185             43870 ns/op
BenchmarkParallelism1-7            32619             38930 ns/op
BenchmarkParallelism1-8            34906             35269 ns/op
BenchmarkParallelism1-9            34266             35647 ns/op
BenchmarkParallelism1-10           35779             36214 ns/op
BenchmarkParallelism1-11           35270             34034 ns/op
BenchmarkParallelism1-12           34281             34716 ns/op
BenchmarkParallelism1-13           33541             34906 ns/op
BenchmarkParallelism1-14           36506             33218 ns/op
BenchmarkParallelism1-15           37736             32134 ns/op
BenchmarkParallelism1-16           35313             33144 ns/op

image.png 前八个CPU基本符合1/n的时间比例,后8核性能提升微弱,出现瓶颈。

不可并行化的程序

不可并行化的程序是指不能被分解成并行执行的子任务的程序。这通常是因为程序的不同部分之间存在依赖关系,或者因为程序需要访问共享资源(如共享内存或共享文件),从而导致并行执行时发生竞争条件和死锁等问题。

以下是一些不可并行化的程序的例子:

  1. 顺序程序:顺序程序是一种不能并行执行的程序,因为它的不同部分之间存在依赖关系。例如,一个简单的计算机程序,它从一个文件读取数据,对数据进行处理,然后将结果写入另一个文件。在这个过程中,读取和写入文件的操作必须按顺序执行,因此无法并行执行。
  2. 递归程序:递归程序也通常不适合并行化,因为递归函数需要按特定的顺序执行,否则会导致错误的结果。例如,一个递归程序可以计算斐波那契数列。虽然可以将计算分成多个部分,但是由于函数依赖于前一个计算结果,因此无法并行执行。
  3. 串行算法:一些算法在串行执行时非常高效,但难以并行化。例如,冒泡排序和插入排序等排序算法就是串行算法,因为它们需要按顺序比较和交换元素。在这种情况下,尝试并行化这些算法可能会导致效率下降,因为并行执行会带来额外的开销。
  4. 某些图形处理程序:某些图形处理程序可能难以并行化,因为它们需要读取和处理整个图像。例如,图像渲染程序通常需要按特定的顺序计算每个像素的颜色值,因此无法并行执行。

总之,不可并行化的程序通常需要更多的时间来执行,因为它们不能利用多个处理器或核心的优势来加速计算。

# 不可并行化的程序速度对比

同样是加法计算,我们加锁限制不能并行化。

func BenchmarkParallelism2(b *testing.B) {
   var mu sync.Mutex
   b.RunParallel(func(pb *testing.PB) {
      for pb.Next() {
         tmp := 0
         for i := 0; i < 1000000; i++ {
            mu.Lock()
            tmp += i
            mu.Unlock()
         }
      }
   })
}

结果如下:

goos: windows
goarch: amd64
pkg: learn/basic/prof/parallel/cpu-num
cpu: AMD Ryzen 7 6800H with Radeon Graphics         
BenchmarkParallelism2                312           3905078 ns/op
BenchmarkParallelism2-2              258           4663807 ns/op
BenchmarkParallelism2-3              195           6148438 ns/op
BenchmarkParallelism2-4              142           8324887 ns/op
BenchmarkParallelism2-5              100          11657833 ns/op
BenchmarkParallelism2-6              100          15447908 ns/op
BenchmarkParallelism2-7              100          21017567 ns/op
BenchmarkParallelism2-8              100          27953532 ns/op
BenchmarkParallelism2-9              100          38174269 ns/op
BenchmarkParallelism2-10             100          48082234 ns/op
BenchmarkParallelism2-11             100          49917040 ns/op
BenchmarkParallelism2-12             100          48796732 ns/op
BenchmarkParallelism2-13             100          49279261 ns/op
BenchmarkParallelism2-14             100          49257185 ns/op
BenchmarkParallelism2-15             100          49384798 ns/op
BenchmarkParallelism2-16             100          48443760 ns/op

image.png

非常amazing,由于加锁的冲突,并行度越高,效率越垃圾。所以不可并行的尽量顺序执行,别加锁。

阿姆达尔定律

阿姆达尔定律(Amdahl's Law)是计算机科学中的一个公式,用于计算在一个任务中,如果有一部分是无法并行化的,那么并行化的效果会受到多大限制。阿姆达尔定律的公式如下:

Speedup=1/[(1P)+(P/N)]Speedup = 1 / [ (1 - P) + (P / N) ]

其中,Speedup是并行化后程序的加速比,P是可以并行化的比例,N是并行处理器的数量。

阿姆达尔定律表明,如果一个程序中有一部分是无法并行化的(比如说是单线程的),那么在增加处理器数量时,加速比的提升将会受到限制。这是因为即使增加处理器的数量,无法并行化的那一部分仍然需要顺序执行,而这一部分的执行时间将会成为瓶颈。因此,增加处理器数量的效果会逐渐减弱,直到无法获得更多的加速比。 所以并行计算并非银弹。