并行思想:提升处理效率

并行思想:提升处理效率

业务场景:

并行思想是一种同时执行多个任务或操作的方法,以提高系统的处理能力和效率。在并行思想中,任务被分解为多个子任务,并且这些子任务可以同时执行,充分利用多核处理器或分布式系统的资源。

案例 1:

智影极速版剪辑器生成视频时,我们会把字幕轨道先合成一个字幕文件并上传到 cos:

因为生成 srt 字幕后还要上传,若串行执行的话,当字幕轨道比较多的时候(比如 10 个)最终的耗时可能就会比较长了。这时,并行处理就能极大地提升效率:

主要使用了 errgroup 这个包,伪代码:

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
package subtitle

import (
true"context"

true"golang.org/x/sync/errgroup"
)

// TracksAsSrt 轨道转字幕
func TracksAsSrt(ctx context.Context, tracks []*Track) (err error) {
trueeg := errgroup.Group{}
truefor i := range tracks {
truetruetrack := tracks[i]
truetrueeg.Go(func() error {
truetruetrue// 生成当前字幕轨的字幕文件名
truetruetruefilename := GetSrtFilename(track)

truetruetrue// 把轨道转为字幕
truetruetruesrt := ConvertTrackToSrt(track)

truetruetrue// 把字幕上传到 cos
truetruetrueif _, err = tools.NewSrtCosHelper().Upload(ctx, filename, srt); err != nil {
truetruetruetruelog.ErrorContextf(ctx, "upload srt failed, filename[%v] err[%v]", filename, err)
truetruetruetruereturn err
truetruetrue}
truetruetruereturn nil
truetrue})
true}
truereturn eg.Wait()
}

性能对比:

简单起见,逻辑处理部分的耗时用 sleep 模拟。

file.go

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
// TracksAsSrtSingle 轨道转字幕(串行)
func TracksAsSrtSingle(ctx context.Context, tracks Tracks) (err error) {
truefor i := range tracks {
truetruei = i
truetruetime.Sleep(100 * time.Millisecond)
true}
truereturn nil
}

// TracksAsSrtBatch 轨道转字幕(并行)
func TracksAsSrtBatch(ctx context.Context, tracks Tracks) (err error) {
trueeg := errgroup.Group{}
truefor i := range tracks {
truetruei = i
truetrueeg.Go(func() error {
truetruetruetime.Sleep(100 * time.Millisecond)
truetruetruereturn nil
truetrue})
true}
truereturn eg.Wait()
}

file_test.go:

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

func BenchmarkTracksAsSrtSingle(b *testing.B) {
truectx := trpc.BackgroundContext()
truefor n := 0; n < b.N; n++ {
truetruetracks := Tracks{
truetruetrue{},
truetruetrue{},
truetruetrue{},
truetruetrue{},
truetruetrue{},
truetruetrue{},
truetruetrue{},
truetruetrue{},
truetruetrue{},
truetruetrue{},
truetrue}
truetrueTracksAsSrtSingle(ctx, tracks)
true}
}

func BenchmarkTracksAsSrtBatch(b *testing.B) {
truectx := trpc.BackgroundContext()
truefor n := 0; n < b.N; n++ {
truetruetracks := Tracks{
truetruetrue{},
truetruetrue{},
truetruetrue{},
truetruetrue{},
truetruetrue{},
truetruetrue{},
truetruetrue{},
truetruetrue{},
truetruetrue{},
truetruetrue{},
truetrue}
truetrueTracksAsSrtBatch(ctx, tracks)
true}
}

压测结果符合预期:并行 10 个的话,性能提升 10 倍:

1
2
3
4
5
6
7
8
cpu: VirtualApple @ 2.50GHz
BenchmarkTracksAsSrtSingle
BenchmarkTracksAsSrtSingle-10 1 1003969084 ns/op 2410792 B/op 19474 allocs/op


cpu: VirtualApple @ 2.50GHz
BenchmarkTracksAsSrtBatch
BenchmarkTracksAsSrtBatch-10 10 100319896 ns/op 226600 B/op 2026 allocs/op

细心的读者已经发现,通过并行处理也能变相地实现批量。不一定非要被下游服务提供一个批量接口(2. 批量思想:解决 N+1 问题)。

谚云:人多力量大

在现代操作系统中,我们可以很方便地编写出多进程的程序。多进程间的通信是需要重点考虑的事项之一,这种通信方式叫作 IPC(Inter- Process Communication)。

在 Linux 操作系统中可以使用的 IPC 方法有很多种。从处理机制的角度看,它们可以分为:

Linux IPC

并发这个概念由来已久,主要思想是使多个任务可以在同一个时间段内执行,以便能够更快地得到结果。

Go 最明显的优势在于拥有基于多线程的并发编程方式。协程有风险,使用须谨慎。协程不是越多越好,当可能出现大量 goroutine 时,可以考虑使用协程池对其管理。ants 是一个高性能且低损耗的 goroutine 池。

0%