闲聊

这个打水印的 demo 其实曾经实现许久,始终没有总结总结,有空填了一下本人的坑吧,也让本人温习温习。

背景

公司是做图形设计资源站点,详情、搜寻页面都须要提供预览图片,图片都是蕴含公司的水印的图片,水印图片独自存储。当初公司须要更换水印图,所以要获取全副的原图,打上新水印,再替换现有的图片。

计划

公司次要是 PHP 开发, 原本是打算用 Laravelcommand 脚本实现的,但想到过后团队也是有动向转 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.644375ms1632*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.06ms1632*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

所以 GoPHP 慢的起因就是呈现在 decode()encode() 这两步了:

  • PHPdecode 两张图片用时 25.34ms,encode 用时 19.63ms
  • Godecode 两张图片用时 60 ms,encode 用时 50ms

图片大小和耗时成正比,测试原图是 1.43 M,如果有晓得起因的小伙伴能够分享分享

协程

尽管我 decodeencode 慢,但我有 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 慢这么多,心愿有晓得的敌人能够交换交换。

总结

  1. Go 提供的工具很不便的找出性能瓶颈,如上文的 PProf
  2. 协程的劣势十分大,在本身函数较慢的状况下,能够充分利用协程施展零碎性能赶超。

参考

《Go 语言高性能编程》pprof 性能剖析