开启掘金成长之旅!这是我参与「掘金日新计划 · 2 月更文挑战」的第 18 天,点击查看活动详情
可并行程序
可并行程序是指可以分解成多个子任务,这些子任务可以同时执行的程序。这些子任务可以在多个处理器或计算节点上并行执行,从而大大缩短程序的执行时间。可并行程序的并行化程度可以从完全并行到完全串行不等。
下面是一些可并行程序的例子:
- 图像处理程序:图像处理通常可以分解成多个子任务,例如图像分割、滤波和增强等。这些子任务可以在多个处理器上并行执行,从而加快图像处理的速度。
- 数值模拟程序:数值模拟通常可以分解成多个子任务,例如离散化、求解线性方程组和解析结果等。这些子任务可以在多个处理器上并行执行,从而加快数值模拟的速度。
- 数据库查询程序:数据库查询通常需要对大量数据进行复杂的计算和检索。这些计算可以分解成多个子任务,例如查询优化、索引构建和数据过滤等。这些子任务可以在多个处理器上并行执行,从而加快查询的速度。
- 分布式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
前八个CPU基本符合1/n的时间比例,后8核性能提升微弱,出现瓶颈。
不可并行化的程序
不可并行化的程序是指不能被分解成并行执行的子任务的程序。这通常是因为程序的不同部分之间存在依赖关系,或者因为程序需要访问共享资源(如共享内存或共享文件),从而导致并行执行时发生竞争条件和死锁等问题。
以下是一些不可并行化的程序的例子:
- 顺序程序:顺序程序是一种不能并行执行的程序,因为它的不同部分之间存在依赖关系。例如,一个简单的计算机程序,它从一个文件读取数据,对数据进行处理,然后将结果写入另一个文件。在这个过程中,读取和写入文件的操作必须按顺序执行,因此无法并行执行。
- 递归程序:递归程序也通常不适合并行化,因为递归函数需要按特定的顺序执行,否则会导致错误的结果。例如,一个递归程序可以计算斐波那契数列。虽然可以将计算分成多个部分,但是由于函数依赖于前一个计算结果,因此无法并行执行。
- 串行算法:一些算法在串行执行时非常高效,但难以并行化。例如,冒泡排序和插入排序等排序算法就是串行算法,因为它们需要按顺序比较和交换元素。在这种情况下,尝试并行化这些算法可能会导致效率下降,因为并行执行会带来额外的开销。
- 某些图形处理程序:某些图形处理程序可能难以并行化,因为它们需要读取和处理整个图像。例如,图像渲染程序通常需要按特定的顺序计算每个像素的颜色值,因此无法并行执行。
总之,不可并行化的程序通常需要更多的时间来执行,因为它们不能利用多个处理器或核心的优势来加速计算。
# 不可并行化的程序速度对比
同样是加法计算,我们加锁限制不能并行化。
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
非常amazing,由于加锁的冲突,并行度越高,效率越垃圾。所以不可并行的尽量顺序执行,别加锁。
阿姆达尔定律
阿姆达尔定律(Amdahl's Law)是计算机科学中的一个公式,用于计算在一个任务中,如果有一部分是无法并行化的,那么并行化的效果会受到多大限制。阿姆达尔定律的公式如下:
其中,Speedup是并行化后程序的加速比,P是可以并行化的比例,N是并行处理器的数量。
阿姆达尔定律表明,如果一个程序中有一部分是无法并行化的(比如说是单线程的),那么在增加处理器数量时,加速比的提升将会受到限制。这是因为即使增加处理器的数量,无法并行化的那一部分仍然需要顺序执行,而这一部分的执行时间将会成为瓶颈。因此,增加处理器数量的效果会逐渐减弱,直到无法获得更多的加速比。 所以并行计算并非银弹。