davidbyttow / govips

A lightning fast image processing and resizing library for Go
MIT License
1.29k stars 199 forks source link

Wired png performance vs pure go thumbnail #143

Open zzjin opened 3 years ago

zzjin commented 3 years ago

Hi there:

I found strage and wired speed perf using govips and pure go package to thumbnail image. test code show here:

package main

import (
    "fmt"
    "image/png"
    "io/ioutil"
    "log"
    "os"
    "time"

    "github.com/davidbyttow/govips/v2/vips"
    "github.com/disintegration/imaging"
)

func checkError(err error) {
    if err != nil {
        fmt.Println("error:", err)
        os.Exit(1)
    }
}

var stime time.Time

func timeSince(t time.Time, s string) {
    e := time.Since(t)
    log.Printf("%s: %s", s, e)
}

func init() {
    // put it here since start up will take nore 10ms,
    // but in real case startup will only trigger once.
    vips.Startup(nil)
}

func main() {
    action, filename := os.Args[1], os.Args[2]

    stime := time.Now()
    if action == "" || action == "govips" {
        govipsRun(filename)
    } else {
        imagingRun(filename)
    }
    timeSince(stime, action)
}

func govipsRun(filename string) {
    img1, err := vips.NewImageFromFile(filename)
    checkError(err)

    err = img1.Thumbnail(300, 300, vips.InterestingCentre)
    checkError(err)

    //default to 6
    ep := vips.NewDefaultPNGExportParams()
    img1bytes, _, err := img1.Export(ep)
    err = ioutil.WriteFile(filename+".govips.png", img1bytes, 0644)
    checkError(err)
}

func imagingRun(filename string) {
    img1, err := imaging.Open(filename)
    checkError(err)

    img1 = imaging.Fill(img1, 300, 300, imaging.Center, imaging.Lanczos)

    // png.DefaultCompression==zlib.DefaultCompression==flate.DefaultCompression==6
    err = imaging.Save(img1, filename+".imaging.png", imaging.PNGCompressionLevel(png.DefaultCompression))
    checkError(err)
}

run script:

go build -a
./imaging_govips imaging image_path
./imaging_govips govips image_path

when test with repo's image files:

../../resources/png-24bit.png
govips: 84.56225ms
imaging: 62.79389ms
../../resources/jpg-24bit.jpg
govips: 12.437344ms
imaging: 12.364945ms

seems when deal png, libvips's version is 35% slower?

PS1: I've searched issues from govips and bimg and libvips founds, it seems to be the Concurrency|vips_concurrency_set related issue?

PS2: in real case I use CompositeMulti and the perf issue grow, which means when I composite 16 images together and save it to file, govips/libvips takes about 2x time. so given code is simplify just for start digg.

tonimelisma commented 3 years ago

Interesting. Would you be willing to do some profiling to start identifying where the difference comes from?

zzjin commented 3 years ago

working on it. But it's bit hard and take more time to find. @see https://github.com/libvips/libvips/issues/1931 https://github.com/libvips/libvips/issues/1919

at this moment, 1: vips_concurrency_set(0) will have better perf, but still slow than pure go, about ~8ms upspeed.<- Here still have some discussion in govips/libvips/bimg issue. https://github.com/jcupitt/libvips/issues/261#issuecomment-92850414 https://github.com/libvips/libvips/issues/639

  1. open with access:VIPS_ACCESS_SEQUENTIAL just for resize and thumbnail will significant speed up, but in real use we cannot open SEQUENTIAL just as govips is do not.(vips_pngload_buffer without more params is random mode).
  2. libvips's real work is done at last vips_pngsave_buffer api, so vips_pngload_buffer and composite just take 0.x ms. <-working on this. Any suggestion will be good.
tonimelisma commented 3 years ago

Hey @zzjin. Thanks a million for the valuable work you're doing.

We do have a PR cooking up that will bring streaming mode to govips: https://github.com/davidbyttow/govips/pull/135

Otherwise I think optimising 15 milliseconds from such an operation sounds tricky. Likely not any single issue you can fix but more optimising lots of small things. All would start from the profiling I guess.

esurdam commented 3 years ago

@zzjin, Just want to note that the resulting output between imaging.Fill() and (*ImageRef).Thumbnail() are distinctly different. (with similar parameters, as shown above). In my testing, the result of Thumbnail fills better (more interesting), and is much closer to what I expect. (and would account for the difference in time)

Do you see similar visual results with both libraries?