背景
go/issues/24499: image/jpeg: Decode is slow
环境变量:
1
2
3
|
Mac OS Sierra
go version go1.10 darwin/amd64
CPU 3,5 GHz Intel Core i7
|
解码 jpeg 大小为 1920x1080 的图像。
测试了 github.com/pixiv/go-libjpeg/jpeg
和 libjpeg。
1
2
|
go 1.10 jpeg.decode ≈ 30 ms cpu ≈ 15 %
libjpeg jpeg.decode ≈ 7 ms cpu ≈ 4 %
|
go 官方会在接下来对此进行优化吗?
优化一
https://go-review.googlesource.com/c/go/+/125138/
分解扫描循环并预先计算,但是这个优化还没有被 Go Review 通过。
来自社区 rbuchell 的“吐槽”
1
2
|
Q(agnivade): Could you please run each benchmark in a quiet machine with browsers shutdown with -count=10, and then compare them with benchstat and post the results ?
A(rbuchell): Sorry, no - I don't have any more time to spend on this. I posted the results on the README, but they are quite easy to run yourself. The benchmarks show the same trend as my original (application) numbers - loading using stb_image via cgo takes around half the time.
|
意思就是说,image 库的性能很差,我已经测试了这个结果,但是我却没有更多的时间,通过 benchstat 比较一下。
不太理解,为什么 rbuchell 如此的不忙?
不过,从 rbuchell 的测试来看,agnivade 发现,C 语言主要是基于 SIMD(SSE2) 做了一些优化。
禁用 SIMD(SSE2) 后,Go 依然会比 C 慢一些。
agnivade 暂时没有想到什么好的算法还可以对此进行优化。所有的数学运算(如霍夫曼解码,idct 等)都有其快速路径,与 C 版本相同。
优化二:减少 idct 和 fdct 的边界检查消除
https://go-review.googlesource.com/c/go/+/167417/
优化:
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
53
54
55
56
57
|
Before -
$gotip build -gcflags="-d=ssa/check_bce/debug=1" fdct.go idct.go
./fdct.go:89:10: Found IsInBounds
./fdct.go:90:10: Found IsInBounds
./fdct.go:91:10: Found IsInBounds
./fdct.go:92:10: Found IsInBounds
./fdct.go:93:10: Found IsInBounds
./fdct.go:94:10: Found IsInBounds
./fdct.go:95:10: Found IsInBounds
./fdct.go:96:10: Found IsInBounds
./idct.go:77:9: Found IsInBounds
./idct.go:77:27: Found IsInBounds
./idct.go:77:45: Found IsInBounds
./idct.go:78:7: Found IsInBounds
./idct.go:78:25: Found IsInBounds
./idct.go:78:43: Found IsInBounds
./idct.go:78:61: Found IsInBounds
./idct.go:79:13: Found IsInBounds
./idct.go:92:13: Found IsInBounds
./idct.go:93:12: Found IsInBounds
./idct.go:94:12: Found IsInBounds
./idct.go:95:12: Found IsInBounds
./idct.go:97:12: Found IsInBounds
./idct.go:98:12: Found IsInBounds
./idct.go:99:12: Found IsInBounds
After -
$gotip build -gcflags="-d=ssa/check_bce/debug=1" fdct.go idct.go
./fdct.go:90:9: Found IsSliceInBounds
./idct.go:76:11: Found IsSliceInBounds
./idct.go:145:11: Found IsSliceInBounds
name old time/op new time/op delta
FDCT-4 1.85µs ± 2% 1.74µs ± 1% -5.95% (p=0.000 n=10+10)
IDCT-4 1.94µs ± 2% 1.89µs ± 1% -2.67% (p=0.000 n=10+9)
DecodeBaseline-4 1.45ms ± 2% 1.46ms ± 1% ~ (p=0.156 n=9+10)
DecodeProgressive-4 2.21ms ± 1% 2.21ms ± 1% ~ (p=0.796 n=10+10)
EncodeRGBA-4 24.9ms ± 1% 25.0ms ± 1% ~ (p=0.075 n=10+10)
EncodeYCbCr-4 26.1ms ± 1% 26.2ms ± 1% ~ (p=0.573 n=8+10)
name old speed new speed delta
DecodeBaseline-4 42.5MB/s ± 2% 42.4MB/s ± 1% ~ (p=0.162 n=9+10)
DecodeProgressive-4 27.9MB/s ± 1% 27.9MB/s ± 1% ~ (p=0.796 n=10+10)
EncodeRGBA-4 49.4MB/s ± 1% 49.1MB/s ± 1% ~ (p=0.066 n=10+10)
EncodeYCbCr-4 35.3MB/s ± 1% 35.2MB/s ± 1% ~ (p=0.586 n=8+10)
name old alloc/op new alloc/op delta
DecodeBaseline-4 63.0kB ± 0% 63.0kB ± 0% ~ (all equal)
DecodeProgressive-4 260kB ± 0% 260kB ± 0% ~ (all equal)
EncodeRGBA-4 4.40kB ± 0% 4.40kB ± 0% ~ (all equal)
EncodeYCbCr-4 4.40kB ± 0% 4.40kB ± 0% ~ (all equal)
name old allocs/op new allocs/op delta
DecodeBaseline-4 5.00 ± 0% 5.00 ± 0% ~ (all equal)
DecodeProgressive-4 13.0 ± 0% 13.0 ± 0% ~ (all equal)
EncodeRGBA-4 4.00 ± 0% 4.00 ± 0% ~ (all equal)
EncodeYCbCr-4 4.00 ± 0% 4.00 ± 0% ~ (all equal)
|
但是这个优化其实对于 jpeg 整体的编解码是没有什么性能提升的。
为什么 idct 没有基于 SSE2 做优化?
Go 团队通常都是坚持使用纯 Go 语言来实现,汇编仅限于加密、数学和高度专用的字节和字符串函数。
更多描述:https://github.com/golang/go/wiki/AssemblyPolicy
首先,AssemblyPolicy(适用于整体 Go 包),同样也适用于 image/**
。
除此之外,学习 Go 的人们通常会阅读标准库代码,因此对于该代码,我们比其他 Go 软件包更喜欢简单性和可读性,而不是原始性能。两种立场都没有错,只是权衡取舍。
如果向标准库中添加少量 SIMD 组件使基准测试性能提高 1.5 倍,那么我可能会接受。
如果添加大量 SIMD 组件使基准测试性能提高了 1.05 倍,那么我可能会拒绝它。
“小”和“大”的意思是主观的。如果没有特定的 SIMD 代码清单,就很难说。
还要注意的是,当我说基准测试时,我主要关注的是整体解码/编码的基准测试,而不仅仅是 FDCT/IDCT 基准测试。用户想要的是解码 JPEG 图像,而不是想直接运行 IDCT。
例如,https://go-review.googlesource.com/c/go/+/167417/2//COMMIT_MSG 对 IDCTs 的基准测试进行了 1.03 倍的改进,但是对整个解码基准测试没有显著的变化。如果这个更改是 SIMD 程序集更改,我将拒绝它,因为与支持程序集的成本相比,它带来的好处太少了。
但是这个 CL 被 merge ,是因为它并没有增加很多的代码,使得程序更复杂。
总结
- Go 团队坚持用纯 Go 实现标准库。
- 不要为了性能,就牺牲简单和可读性。
参考资料
- https://github.com/golang/go/issues/24499
- https://github.com/golang/go/wiki/AssemblyPolicy
茶歇驿站
一个可以让你停下来看一看,在茶歇之余给你帮助的小站,这里的内容主要是后端技术,个人管理,团队管理,以及其他个人杂想。