使用 Go 進行平行運算

前言

本文實作一個可以將數字陣列加總的平行運算,並進行效能測試。

做法

首先,建立一個 nums() 函式,生成特定數量元素的陣列,例如一個包含 1 至 100 的陣列。

1
2
3
4
5
6
7
8
9
func nums(amount int) []int {
nums := []int{}

for i := 1; i <= amount; i++ {
nums = append(nums, i)
}

return nums
}

建立一個 sum() 函式,將陣列中所有的元素加總。

1
2
3
4
5
6
7
8
9
func sum(nums []int) int {
total := 0

for _, n := range nums {
total += n
}

return total
}

建立一個 chunk() 函式,將陣列拆成小塊。

1
2
3
4
5
6
7
func chunk(nums []int, size int) (chunks [][]int) {
for size < len(nums) {
nums, chunks = nums[size:], append(chunks, nums[0:size:size])
}

return append(chunks, nums)
}

建立一個 sumParallel() 函式,使用特定數量的 goroutine 來將陣列中的所有元素加總。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
func sumParallel(nums []int, concurrency int) int {
ch := make(chan int)
chunks := chunk(nums, len(nums)/concurrency)

for i := 0; i < concurrency; i++ {
go func(i int) {
ch <- sum(chunks[i])
}(i)
}

total := 0

for i := 0; i < concurrency; i++ {
total += <-ch
}

return total
}

在主程式中使用。

1
2
3
4
5
6
7
func main() {
nums := nums(10)

total := sumParallel(nums, 2)

log.Println(total) // 55
}

單元測試

建立測試案例。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
func TestSum(t *testing.T) {
nums := nums(60)

if sum(nums) != 1830 {
t.Fail()
}
}

func TestSumParallel2(t *testing.T) {
nums := nums(60)

if sumParallel(nums, 2) != 1830 {
t.Fail()
}
}

func TestSumParallel6(t *testing.T) {
nums := nums(60)

if sumParallel(nums, 6) != 1830 {
t.Fail()
}
}

執行單元測試:

1
go test -v .

效能測試

建立 Benchmark 案例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
func BenchmarkSum(b *testing.B) {
nums := nums(600000)

for i := 0; i < b.N; i++ {
sum(nums)
}
}

// 使用 2 個 goroutine
func BenchmarkSumParallel2(b *testing.B) {
nums := nums(600000)

for i := 0; i < b.N; i++ {
sumParallel(nums, 2)
}
}

// 使用 4 個 goroutine
func BenchmarkSumParallel4(b *testing.B) {
nums := nums(600000)

for i := 0; i < b.N; i++ {
sumParallel(nums, 4)
}
}

// 使用 6 個 goroutine
func BenchmarkSumParallel6(b *testing.B) {
nums := nums(600000)

for i := 0; i < b.N; i++ {
sumParallel(nums, 6)
}
}

// 使用 8 個 goroutine
func BenchmarkSumParallel8(b *testing.B) {
nums := nums(600000)

for i := 0; i < b.N; i++ {
sumParallel(nums, 8)
}
}

// 使用 10 個 goroutine
func BenchmarkSumParallel10(b *testing.B) {
nums := nums(600000)

for i := 0; i < b.N; i++ {
sumParallel(nums, 10)
}
}

執行效能測試:

1
go test -v -bench=. -benchmem .

結果:

1
2
3
4
5
6
7
8
9
10
11
12
BenchmarkSum
BenchmarkSum-12 5222 222150 ns/op 5519 B/op 0 allocs/op
BenchmarkSumParallel2
BenchmarkSumParallel2-12 9115 124736 ns/op 3340 B/op 3 allocs/op
BenchmarkSumParallel4
BenchmarkSumParallel4-12 16198 73768 ns/op 2052 B/op 4 allocs/op
BenchmarkSumParallel6
BenchmarkSumParallel6-12 19330 63036 ns/op 1956 B/op 5 allocs/op
BenchmarkSumParallel8
BenchmarkSumParallel8-12 16623 71109 ns/op 2198 B/op 5 allocs/op
BenchmarkSumParallel10
BenchmarkSumParallel10-12 17088 72463 ns/op 2536 B/op 6 allocs/op

程式碼