Convert is slow, when the image pixel size is large.
Why, Background
I am developing an image server at my company. At that time, I chose 'bimg' for image processing.
The image processing we use is Resize, Extract, Convert. Among them, this issue is related to the performance of Convert.
Trouble we have
When the image whose pixel size is large be converted, processing takes longer than the small image.
I want to know how to convert a large image at high speed. (Is it the same in the first place?)
Benchmark
- Convert the large image and the small image from png to webp respectively, and take the benchmark.
- The large image is 3MB, 900x500px.
- The small image is 3MB, 6000x4000px.
- PC specs is Apple M1, 16 MB, 8 core.
// target function. https://github.com/h2non/bimg#convert
func resizeAndConvert(b *testing.B, filename string) {
b.Helper()
buffer, err := bimg.Read(filename)
if err != nil {
b.Fatalf("failed to read file. err: %v", err)
}
src := bimg.NewImage(buffer)
src.Resize(900, 500)
src.Convert(bimg.WEBP)
}
// benchmark of convert()
// 3MB, 900x500px
func BenchmarkConvertWithSmallSize(b *testing.B) {
for i := 0; i < b.N; i++ {
convert("./small.png")
}
}
// 3MB, 6000x4000px
func BenchmarkConvertWithLargeSize(b *testing.B) {
for i := 0; i < b.N; i++ {
convert("./large.png")
}
}
result
$ go test -bench . -benchmem
goos: darwin
goarch: arm64
pkg: sandbox/bimg-benchmark
BenchmarkConvertWithSmallSize-8 25 40814317 ns/op 565760 B/op 13 allocs/op
BenchmarkConvertWithLargeSize-8 1 1494017500 ns/op 4858272 B/op 13 allocs/op
PASS
ok sandbox/bimg-benchmark 2.098s
Try to resize before converting
TL;DR
It was a success. The benchmark was improved. 🎉
benchmark
func resizeAndConvert(b *testing.B, filename string) {
b.Helper()
buffer, err := bimg.Read(filename)
if err != nil {
b.Fatalf("failed to read file. err: %v", err)
}
src := bimg.NewImage(buffer)
src.Resize(900, 500)
src.Convert(bimg.WEBP)
}
// benchmark of convert()
// 3MB, 900x500px
func BenchmarkConvertWithSmallSize(b *testing.B) {
for i := 0; i < b.N; i++ {
resizeAndConvert(b, "./small.png")
}
}
// 3MB, 6000x4000px
func BenchmarkConvertWithLargeSize(b *testing.B) {
for i := 0; i < b.N; i++ {
resizeAndConvert(b, "./large.png")
}
}
$ go test -bench . -benchmem
goos: darwin
goarch: arm64
pkg: sandbox/bimg-benchmark
BenchmarkConvertWithSmallSize-8 8 131805583 ns/op 1081849 B/op 21 allocs/op
BenchmarkConvertWithLargeSize-8 14 80117911 ns/op 3597262 B/op 32 allocs/op
📝 remarks
Resize of small.png became slower.
If it's small enough, we shouldn't resize it? 🤔
https://github.com/h2non/bimg/issues/395#issuecomment-1028558740
:wip
https://github.com/h2non/bimg/issues/395#issuecomment-1023130350
📝 remarks Resize of small.png became slower. If it's small enough, we shouldn't resize it.
Fact
- Resize to same size(px) takes longer time than usual.
- We should avoid resizing to same size(px).
why?
// target function.
func resize(b *testing.B, filename string, w int, h int) {
b.Helper()
buffer, err := bimg.Read(filename)
if err != nil {
b.Fatalf("failed to read file. err: %v", err)
}
src := bimg.NewImage(buffer)
src.Resize(w, h)
}
// 3MB, 900x500px
func BenchmarkResizeWithSmallSize(b *testing.B) {
for i := 0; i < b.N; i++ {
// example 900x500px
resize(b, "./small.png", 900, 500)
}
}
// 3MB, 6000x4000px
func BenchmarkResizeWithLargeSize(b *testing.B) {
for i := 0; i < b.N; i++ {
// example 900x500px
resize(b, "./large.png", 900, 500)
}
}
# resize to 900x500px (it is the same as small.png)
BenchmarkResizeWithSmallSize-8 12 89920510 ns/op
BenchmarkResizeWithLargeSize-8 22 50316055 ns/op
# resize to 6000x4000px (it is the same as large.png)
BenchmarkResizeWithSmallSize-8 12 89839004 ns/op
BenchmarkResizeWithLargeSize-8 6 200031826 ns/op
# resize to 500x200px
BenchmarkResizeWithSmallSize-8 42 27480676 ns/op
BenchmarkResizeWithLargeSize-8 30 37676425 ns/op
@igsr5 with bimg v1, the two operations (.Resize(...) and .Convert()) both load, transform and then encode the image. That's also why the first resize is so slow, because it needs to encode PNG (which is generally rather slow depending on the image).
If you feel adventerous, you could try the in development v2 (from the v2-dev branch; using go get github.com/h2non/bimg/v2@v2-dev if I am not mistaken). There the transformations happen on the decoded image. You would do something like:
img, err := bimg.NewImageFromFile(filename)
if err != nil {
panic(err)
}
if err := img.Resize(bimg.ResizeOptions{Width: 900, Height: 500}; err != nil {
panic(err)
}
b, err := img.Save(bimg.SaveOptions{Type: bimg.WEBP})
if err != nil {
panic(err)
}
// do something with the byte buffer b