共计 3000 个字符,预计需要花费 8 分钟才能阅读完成。
闲聊
这个打水印的 demo
其实曾经实现许久,始终没有总结总结,有空填了一下本人的坑吧,也让本人温习温习。
背景
公司是做图形设计资源站点,详情、搜寻页面都须要提供预览图片,图片都是蕴含公司的水印的图片,水印图片独自存储。当初公司须要更换水印图,所以要获取全副的原图,打上新水印,再替换现有的图片。
计划
公司次要是 PHP
开发, 原本是打算用 Laravel
的 command
脚本实现的,但想到过后团队也是有动向转 Go
方向并且 Go
在有协程的加持,能够开多个协程来执行打水印这部分工作,所以最终采纳了 Go
语言来实现这个需要,算是一个的新的尝试。
实现
整体流程是:遍历数据库数据 -> 获取原图信息 -> 下载原图 -> 生成水印 -> 上传水印图 -> 更新数据库水印图片,本文次要叙述 生成水印 此步骤,因为这一块与 PHP
的实现上略有不同,在 Go
上可应用协程并发解决,而 PHP
只能单个解决。
Go 实现
go version go1.17.7 darwin/arm64
同步
同步执行流程与 PHP
一样,比照一下两者效率
main.go
// 同步 | |
func syncGen() {startT := time.Now() | |
// 关上水印图 | |
water, err := pkg.OpenPngImage("./test_water.png") | |
if err != nil {log.Fatal(err) | |
} | |
// 这个循环是遍历原图 | |
imgPath := "./test.png" | |
for i := 1; i <= num; i++ { | |
id := i + 1000 // 模仿 id | |
if err = pkg.Generate(imgPath, id, water); err != nil {log.Println("水印图生成失败,id=" + strconv.Itoa(i)) | |
} | |
} | |
// 计算耗时 | |
tc := time.Since(startT) | |
fmt.Printf("同步执行工夫 = %v\n", tc) | |
} |
go run main.go -n 1 | |
水印图:1 张 | |
同步执行工夫 = 109.644375ms | |
1632*874 JPEG(24 位色彩) 78.87kb |
PHP 实现
PHP 7.4.28 (cli)
长期写了个 PHP
版本的同性能 demo
$t1 = microtime(true); | |
$dst = '/home/jiumu/code/go-image-water-case/test1.png'; | |
$src = '/home/jiumu/code/go-image-water-case/test_water1.png'; | |
$font = '/home/jiumu/code/go-image-water-case/font.ttf'; | |
$t3 = microtime(true); | |
$srcIm = imagecreatefrompng($src); | |
$t4 = microtime(true); | |
$num = 1; | |
for ($i = 0; $i < $num; $i++) {$t5 = microtime(true); | |
$dstIm = imagecreatefrompng($dst); | |
$t6 = microtime(true); | |
$dstSize = getimagesize($dst); | |
$srcSize = getimagesize($src); | |
$new = imagecreatetruecolor($dstSize[0], $dstSize[1]); | |
imagecopy($new, $dstIm, 0, 0, 0, 0, $dstSize[0], $dstSize[1]); | |
imagecopy($new, $srcIm, $dstSize[0] - 200, $dstSize[1] - 220, 0, 0, $srcSize[0], $srcSize[1]); | |
$rgb = imagecolorallocate($dstIm, 21, 33, 57);// 字体色彩 | |
$id = $i + 1000; | |
imagefttext($new, 30, 0, $dstSize[0] - 200, $dstSize[1] - 20, $rgb, $font, "ID: {$id}"); | |
$t7 = microtime(true); | |
imagejpeg($new, __DIR__ . "/image/{$i}.jpeg"); | |
$t8 = microtime(true); | |
imagedestroy($dstIm); | |
imagedestroy($new); | |
} | |
imagedestroy($srcIm); | |
$t2 = microtime(true); | |
echo '总耗时:' . round($t2 - $t1, 5). 's' . PHP_EOL; | |
echo '总耗时:' . round($t2 - $t1, 5) * 1000 . 'ms' . PHP_EOL; |
后果
$ php index.php | |
总耗时: 0.05506s | |
总耗时: 55.06ms | |
1632*874 JPEG(24 位色彩) 78.82kb |
PHP 既然比 Go 快了近一倍?难道是我 Go 的代码写的有问题?起初发现 PHP 生成的图片品质只有 Go 的 75% 的品质,所以把 Go 这边的品质降到了 75%,效率仍然没有变动,在束手无策的时候,想到了 PProf
找一下性能到底卡在哪里了。
生成 1 张水印图 pprof 的局部截图
找出耗时起因了,原来耗时的都卡在了 png.Decode()
和 jpeg.Encode()
这两步了,再来看看 PHP
这两步的工夫
php index.php | |
总耗时: 55.06ms | |
水印 decode 耗时: 0.87ms | |
原图 decode 耗时: 24.47ms | |
新图 encode 耗时: 19.63ms |
所以 Go
比 PHP
慢的起因就是呈现在 decode()
和 encode()
这两步了:
PHP
在decode
两张图片用时 25.34ms,encode 用时 19.63msGo
在decode
两张图片用时 60 ms,encode 用时 50ms
图片大小和耗时成正比,测试原图是 1.43 M,如果有晓得起因的小伙伴能够分享分享
协程
尽管我 decode
和 encode
慢,但我有 goroutine
啊,来看看 Go
的体现
各生成 100 张水印图:
php
总耗时: 5.34911s | |
总耗时: 5349.11ms |
go
NumCPU:8
NumGoroutine: 102
$ go run main.go -n 100 | |
水印图:100 张 | |
同步执行工夫 = 9.583812958s | |
无并发数量管制工夫 = 2.072805143s |
协程状态下比 PHP 快了 2.5 倍,同步状况下,还是比 php 慢 2 倍。
协程管制
因为启动的 goroutine 协程不受管制,如果 panic 了则无奈解决,成为家养携程,导致程序挂掉。
// Go 防止 go func(){} 如果办法中抛出 panic 无奈被捕捉到 | |
// 或者是每在每个 go 后面都 recover() 一次,造成的代码凌乱不可保护 | |
func Go(f func()) {defer func() {if err := recover(); err != nil { | |
// 记录日志 | |
log.Println(err) | |
} | |
}() | |
go f()} |
开启协程的形式则变成:
pkg.Go(func() {// code})
因为代码篇幅较长,所以代码没有贴出来,有趣味的小伙伴能够到此查看源码 https://github.com/zxr615/go-image-water-case
Go 的 decode
/ encode
为何绝对 php
慢这么多,心愿有晓得的敌人能够交换交换。
总结
Go
提供的工具很不便的找出性能瓶颈,如上文的PProf
- 协程的劣势十分大,在本身函数较慢的状况下,能够充分利用协程施展零碎性能赶超。
参考
《Go 语言高性能编程》pprof 性能剖析