mirror of
https://github.com/navidrome/navidrome.git
synced 2026-08-01 07:21:17 +00:00
test(artwork): benchmark both hash encoders from one file
benchImage and BenchmarkEncodeAtInputSize existed in both encoder packages, and blurhash's copy had drifted into a third inline duplicate once both benches moved to NRGBA input. The head-to-head now lives in core/artwork, which already imports both encoders and can pin the input to the real thumbnailSize constant. The gradient builder moves to tests, which both packages already import via their suite bootstrap. thumbhash keeps a shipped-vs-reference benchmark, since referenceEncode is test-only and cannot be reached from core/artwork.
This commit is contained in:
parent
ecf12b269f
commit
9084685fbc
@ -1,63 +0,0 @@
|
||||
package blurhash_test
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"image"
|
||||
"image/color"
|
||||
"testing"
|
||||
|
||||
"github.com/navidrome/navidrome/core/artwork/blurhash"
|
||||
)
|
||||
|
||||
// benchImage builds a deterministic gradient so runs are comparable across revisions.
|
||||
func benchImage(size int) image.Image {
|
||||
img := image.NewNRGBA(image.Rect(0, 0, size, size))
|
||||
for y := 0; y < size; y++ {
|
||||
for x := 0; x < size; x++ {
|
||||
img.SetNRGBA(x, y, color.NRGBA{
|
||||
R: uint8(255 * x / size),
|
||||
G: uint8(255 * y / size),
|
||||
B: uint8((x + y) * 255 / (2 * size)),
|
||||
A: 255,
|
||||
})
|
||||
}
|
||||
}
|
||||
return img
|
||||
}
|
||||
|
||||
// BenchmarkEncodeAtInputSize mirrors the thumbhash bench of the same name. *image.NRGBA, matching
|
||||
// makeThumbnail's output, so neither package is measured with a conversion the other avoids.
|
||||
func BenchmarkEncodeAtInputSize(b *testing.B) {
|
||||
const size = 100
|
||||
img := image.NewNRGBA(image.Rect(0, 0, size, size))
|
||||
for y := range size {
|
||||
for x := range size {
|
||||
img.SetNRGBA(x, y, color.NRGBA{
|
||||
R: uint8(255 * x / size),
|
||||
G: uint8(255 * y / size),
|
||||
B: uint8((x + y) * 255 / (2 * size)),
|
||||
A: 255,
|
||||
})
|
||||
}
|
||||
}
|
||||
b.ReportAllocs()
|
||||
for range b.N {
|
||||
if _, err := blurhash.Encode(img); err != nil {
|
||||
b.Fatal(err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkEncode(b *testing.B) {
|
||||
for _, size := range []int{100, 300, 600, 900, 1200, 1500} {
|
||||
img := benchImage(size)
|
||||
b.Run(fmt.Sprintf("%dx%d", size, size), func(b *testing.B) {
|
||||
b.ReportAllocs()
|
||||
for range b.N {
|
||||
if _, err := blurhash.Encode(img); err != nil {
|
||||
b.Fatal(err)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
52
core/artwork/hash_encoder_bench_test.go
Normal file
52
core/artwork/hash_encoder_bench_test.go
Normal file
@ -0,0 +1,52 @@
|
||||
package artwork
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"image"
|
||||
"testing"
|
||||
|
||||
"github.com/navidrome/navidrome/core/artwork/blurhash"
|
||||
"github.com/navidrome/navidrome/core/artwork/thumbhash"
|
||||
"github.com/navidrome/navidrome/tests"
|
||||
)
|
||||
|
||||
// hashEncoders are the two placeholder hashes decodeArtwork computes from one shared thumbnail.
|
||||
var hashEncoders = []struct {
|
||||
name string
|
||||
encode func(image.Image) error
|
||||
}{
|
||||
{"blurhash", func(img image.Image) error { _, err := blurhash.Encode(img); return err }},
|
||||
{"thumbhash", func(img image.Image) error { _, err := thumbhash.Encode(img); return err }},
|
||||
}
|
||||
|
||||
func benchEncoder(b *testing.B, encode func(image.Image) error, img image.Image) {
|
||||
b.Helper()
|
||||
b.ReportAllocs()
|
||||
for b.Loop() {
|
||||
if err := encode(img); err != nil {
|
||||
b.Fatal(err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// BenchmarkHashEncodersAtInputSize is the bar: both encoders are handed the identical image
|
||||
// makeThumbnail produces, so neither is measured with a conversion the other avoids.
|
||||
func BenchmarkHashEncodersAtInputSize(b *testing.B) {
|
||||
img := tests.GradientImage(thumbnailSize)
|
||||
for _, e := range hashEncoders {
|
||||
b.Run(e.name, func(b *testing.B) { benchEncoder(b, e.encode, img) })
|
||||
}
|
||||
}
|
||||
|
||||
// BenchmarkHashEncoders sweeps past the pipeline's input size, where each package's own defensive
|
||||
// downscale starts to dominate.
|
||||
func BenchmarkHashEncoders(b *testing.B) {
|
||||
for _, size := range []int{100, 300, 600, 900, 1200, 1500} {
|
||||
img := tests.GradientImage(size)
|
||||
for _, e := range hashEncoders {
|
||||
b.Run(fmt.Sprintf("%s/%dx%d", e.name, size, size), func(b *testing.B) {
|
||||
benchEncoder(b, e.encode, img)
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1,66 +1,36 @@
|
||||
package thumbhash_test
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"image"
|
||||
"image/color"
|
||||
"testing"
|
||||
|
||||
"github.com/navidrome/navidrome/core/artwork/thumbhash"
|
||||
"github.com/navidrome/navidrome/tests"
|
||||
)
|
||||
|
||||
// benchImage builds the same deterministic gradient the blurhash bench uses, as *image.NRGBA —
|
||||
// the concrete type makeThumbnail hands both encoders in production.
|
||||
func benchImage(size int) *image.NRGBA {
|
||||
img := image.NewNRGBA(image.Rect(0, 0, size, size))
|
||||
// BenchmarkEncodeVsReference pins the two-pass separable encoder against the naive reference port
|
||||
// it replaced. referenceEncode is test-only, so this cannot live beside the cross-encoder
|
||||
// benchmark in core/artwork.
|
||||
func BenchmarkEncodeVsReference(b *testing.B) {
|
||||
const size = 100
|
||||
img := tests.GradientImage(size)
|
||||
pix := make([]byte, 0, size*size*4)
|
||||
for y := range size {
|
||||
for x := range size {
|
||||
img.SetNRGBA(x, y, color.NRGBA{
|
||||
R: uint8(255 * x / size),
|
||||
G: uint8(255 * y / size),
|
||||
B: uint8((x + y) * 255 / (2 * size)),
|
||||
A: 255,
|
||||
})
|
||||
}
|
||||
pix = append(pix, img.Pix[y*img.Stride:y*img.Stride+size*4]...)
|
||||
}
|
||||
return img
|
||||
}
|
||||
|
||||
// BenchmarkEncodeAtInputSize is the bar: 100x100 is exactly what the artwork pipeline feeds.
|
||||
func BenchmarkEncodeAtInputSize(b *testing.B) {
|
||||
img := benchImage(100)
|
||||
b.ReportAllocs()
|
||||
for range b.N {
|
||||
if _, err := thumbhash.Encode(img); err != nil {
|
||||
b.Fatal(err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkEncode(b *testing.B) {
|
||||
for _, size := range []int{100, 300, 600, 900, 1200, 1500} {
|
||||
img := benchImage(size)
|
||||
b.Run(fmt.Sprintf("%dx%d", size, size), func(b *testing.B) {
|
||||
b.ReportAllocs()
|
||||
for range b.N {
|
||||
if _, err := thumbhash.Encode(img); err != nil {
|
||||
b.Fatal(err)
|
||||
}
|
||||
b.Run("shipped", func(b *testing.B) {
|
||||
b.ReportAllocs()
|
||||
for b.Loop() {
|
||||
if _, err := thumbhash.Encode(img); err != nil {
|
||||
b.Fatal(err)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
// BenchmarkReferenceEncode is the naive baseline: four w*h scratch arrays and 40 pixel re-reads.
|
||||
func BenchmarkReferenceEncode(b *testing.B) {
|
||||
img := benchImage(100)
|
||||
w, h := 100, 100
|
||||
pix := make([]byte, 0, w*h*4)
|
||||
for y := range h {
|
||||
pix = append(pix, img.Pix[y*img.Stride:y*img.Stride+w*4]...)
|
||||
}
|
||||
b.ReportAllocs()
|
||||
for range b.N {
|
||||
_ = referenceEncode(w, h, pix)
|
||||
}
|
||||
b.Run("reference", func(b *testing.B) {
|
||||
b.ReportAllocs()
|
||||
for b.Loop() {
|
||||
_ = referenceEncode(size, size, pix)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
23
tests/images.go
Normal file
23
tests/images.go
Normal file
@ -0,0 +1,23 @@
|
||||
package tests
|
||||
|
||||
import (
|
||||
"image"
|
||||
"image/color"
|
||||
)
|
||||
|
||||
// GradientImage builds a deterministic square gradient, so benchmark runs are comparable across
|
||||
// revisions. NRGBA is the type the artwork pipeline's makeThumbnail hands its hash encoders.
|
||||
func GradientImage(size int) *image.NRGBA {
|
||||
img := image.NewNRGBA(image.Rect(0, 0, size, size))
|
||||
for y := range size {
|
||||
for x := range size {
|
||||
img.SetNRGBA(x, y, color.NRGBA{
|
||||
R: uint8(255 * x / size),
|
||||
G: uint8(255 * y / size),
|
||||
B: uint8((x + y) * 255 / (2 * size)),
|
||||
A: 255,
|
||||
})
|
||||
}
|
||||
}
|
||||
return img
|
||||
}
|
||||
Loading…
x
Reference in New Issue
Block a user