优化 Go 图片转换程序性能
一
根据公司的业务需求,使用 Go 编写了一个程序,主要功能是将 HTML 文件中的图片链接提取出来,把其中的 svg 图片下载并转换为 png/jpeg 格式图片,然后插入到原文件中返回
其中使用到了 libvips
库进行图片转换操作,这个库相对 ImageMagick
速度更快,结合 Go 的协程特性可以大幅加快程序的运行时间
使用 govips 库进行开发,转换相关设置的代码如下
const (
// 控制Go协程并发量
concurrency = 50
// 控制Vips线程数量
DefaultVipsConcurrency = 4
// 控制Vips最大缓存
DefaultVipsMaxCacheMem = 1024 * 1024 * 1024
)
func vipsRun(data []byte) ([]byte, error) {
// ...
// 转换jpeg格式图片时
opt := vips.NewJpegExportParams()
inImg.Flatten(&vips.Color{R: 255, G: 255, B: 255})
outBuffer, _, err := inImg.ExportJpeg(opt)
// 转换png格式图片时
opt := vips.PngExportParams{
Compression: 9,
Filter: vips.PngFilterNone,
Quality: 50.
}
outBuffer, _, err := inImg.ExportPng(&opt)
// ...
}
二
测试使用的文件总计包含 1249 张图片链接
开发机拥有 16 线程的 CPU,导出 jpg 格式,总计耗时为 10s
如果转换为 png 格式,导出耗时提升到了 18s
测试线是一台 4 线程的服务器,同样的设置下导出 jpg 耗时为 52s
导出 png 耗时为 65s
三
根据上面的测试可以看出:
- 处理 jpg 格式的性能高于 png 格式
- 程序执行的时候如果不加以限制,将会跑满所有 CPU 的核心,可能直接导致服务器中的其他服务停止响应
- CPU 线程数对于本程序执行时间的影响非常大
对于问题 1 由于导出图片对透明通道没有强制需求,可以固定导出为 jpg 格式图片,通过增大图片质量设置等获取一个较高的清晰度(满足打印清晰的需求即可)
对于问题 2 可以用以下命令在执行时手动限制程序运行的 CPU 核心以此限制其对于服务器资源的无限制占用
# 将命令在CPU0、1上运行
taskset -c 0,1 go run .
# 将7013(PID)进程在CPU0、1上运行
taskset -p 0,1 7013
对于问题 3 恐怕只有增加服务器性能一条路了,无奈优化到最后还是物理层面的原因限制了最终的性能
以及记录一些其它的优化:
- 使用
github.com/panjf2000/ants/v2
库,用池化方法创建协程来节省资源 - 使用
runtime.GOMAXPROCS(1)
来手动设置当前进程执行使用的最大 CPU 数(此项设置似乎并不生效) - 第三方调用程序时使用流的方式输入和输出数据而不使用文件读写
四
或许换一个思路来说,如此耗时耗能的功能是否不应该放在服务端执行?是否可以移植到客户端消耗客户端设备的资源来运行?上面的测试只是执行一次导出一份的结果,如果多用户同时执行此程序恐怕会导致灾难级的结果
对这个程序的优化还没有完成,接下来有四个思路来进行优化:
- 将此方法放到客户端(前端和 APP 端)来执行,需要对 WASM 的兼容性做测试
- 增加异步消息队列来缓解高并发下的问题
- 从根源入手:执行一个脚本程序在服务器不间断自动转换所有 svg 图片,这样导出时只需要处理下载图片的流程
- 先提升服务器核心数使用此程序暂时应对
参考
- https://docs.redhat.com/zh_hans/documentation/red_hat_enterprise_linux_for_real_time/9/html/optimizing_rhel_9_for_real_time_for_low_latency_operation/proc_binding-processes-to-cpus-using-the-taskset-utility_assembly_binding-interrupts-and-processes
- https://github.com/libvips/libvips/issues/561
- https://www.libvips.org/API/current/VipsForeignSave.html#vips-jpegsave