draw2d icon indicating copy to clipboard operation
draw2d copied to clipboard

Performance is ~10-30x worse than Cairo

Open usedbytes opened this issue 7 years ago • 3 comments

Perhaps this doesn't qualify as an issue per-se, but I wanted to use draw2d as the renderer in my program (instead of Cairo). However, even in simple cases, draw2dimg performs at least 10-30x worse than the equivalent Cairo code via cgo.

Maybe I'm doing something wrong? (I hope so)

Simple benchmark test below draws filled squares with an outline, performing ~30x worse on draw2d than Cairo. When the drawn surface is 1:1 blitted to another surface, the gap closes to ~10x

package main

import (
	"flag"
	"image"
	"image/color"
	"image/png"
	"log"
	"os"
	"testing"

	"github.com/ungerik/go-cairo"
	"github.com/llgcode/draw2d/draw2dimg"
)

var dumpImage bool

func TestMain(m *testing.M) {
	flag.BoolVar(&dumpImage, "dump", false, "Dump a PNG image from each benchmark for comparison")
	flag.Parse()

	os.Exit(m.Run())
}

func BenchmarkDraw2D(b *testing.B) {
	img := image.NewRGBA(image.Rect(0, 0, 500, 500))
	ctx := draw2dimg.NewGraphicContext(img)

	for n := 0; n < b.N; n++ {
		ctx.SetStrokeColor(color.RGBA{0xff, 0x00, 0x00, 0xff})
		ctx.SetFillColor(color.RGBA{0x4d, 0x4d, 0x4d, 0xff})
		ctx.SetLineWidth(2)
		ctx.MoveTo(1, 1)
		ctx.LineTo(499, 1)
		ctx.LineTo(499, 499)
		ctx.LineTo(1, 499)
		ctx.Close()
		ctx.FillStroke()
	}

	if dumpImage {
		f, err := os.Create("draw2d.png")
		if err != nil {
			log.Fatal(err)
		}

		if err := png.Encode(f, img); err != nil {
			f.Close()
			log.Fatal(err)
		}

		if err := f.Close(); err != nil {
			log.Fatal(err)
		}
	}
}

func BenchmarkCairo(b *testing.B) {
	img := cairo.NewSurface(cairo.FORMAT_ARGB32, 500, 500)

	for n := 0; n < b.N; n++ {

		img.Rectangle(0, 0, 500, 500)
		img.SetSourceRGB(0.3, 0.3, 0.3)
		img.SetLineWidth(4)
		img.FillPreserve()
		img.SetSourceRGB(1.0, 0.0, 0.0)
		img.Stroke()
	}

	if dumpImage {
		img.WriteToPNG("cairo.png")
	}
}

Results:

$ go test -v -bench=. -test.parallel 1
goos: linux
goarch: amd64
pkg: github.com/usedbytes/drawbench
BenchmarkDraw2D-4           1000           2049011 ns/op
BenchmarkCairo-4           30000             50037 ns/op
PASS
ok      github.com/usedbytes/drawbench  4.278s

The README says that draw2d is a pure-go alternative to Cairo, but unfortunately the performance difference is too high for my use-case.

usedbytes avatar Sep 13 '18 18:09 usedbytes

Any idea on what the bottleneck might be? Comparable performance would be a long-term goal.

redstarcoder avatar Sep 16 '18 08:09 redstarcoder

Thanks for the benchmark comparison. I need to make more test but, I feel it comes from the fill operation. I didn't work a lot on performance. The package I use for this is https://github.com/golang/freetype/raster

llgcode avatar Sep 16 '18 20:09 llgcode

Yes, it does seem to be the raster code (*RGBAPainter).Paint

Flat Flat% Sum% Cum Cum% Name Inlined?
2.20s 95.24% 95.24% 2.20s 95.24% github.com/golang/freetype/raster.(*RGBAPainter).Paint  
0.04s 1.73% 96.97% 0.04s 1.73% github.com/golang/freetype/raster.(*Rasterizer).findCell  
0.04s 1.73% 98.70% 2.24s 96.97% github.com/golang/freetype/raster.(*Rasterizer).Rasterize  
0.01s 0.43% 99.13% 0.05s 2.16% github.com/golang/freetype/raster.(*Rasterizer).saveCell  
0.01s 0.43% 99.57% 0.06s 2.60% github.com/golang/freetype/raster.(*Rasterizer).setCell  
0 0.00% 99.57% 2.30s 99.57% github.com/llgcode/draw2d/draw2dimg.(*GraphicContext).FillStroke  
0 0.00% 99.57% 0.06s 2.60% github.com/llgcode/draw2d/draw2dimg.(*FtLineBuilder).LineTo  
0 0.00% 99.57% 0.06s 2.60% github.com/llgcode/draw2d/draw2dbase.Transformer.LineTo  
0 0.00% 99.57% 0.06s 2.60% github.com/llgcode/draw2d/draw2dbase.Flatten  
0 0.00% 99.57% 0.03s 1.30% github.com/llgcode/draw2d/draw2dbase.DemuxFlattener.LineTo  
0 0.00% 99.57% 2.24s 96.97% github.com/llgcode/draw2d/draw2dimg.(*GraphicContext).paint  
0 0.00% 99.57% 0.06s 2.60% github.com/llgcode/draw2d/draw2dbase.(*Transformer).LineTo  
0 0.00% 99.57% 0.03s 1.30% github.com/llgcode/draw2d/draw2dbase.(*LineStroker).End  
0 0.00% 99.57% 0.03s 1.30% github.com/llgcode/draw2d/draw2dbase.(*DemuxFlattener).LineTo  
0 0.00% 99.57% 0.03s 1.30% github.com/llgcode/draw2d/draw2dbase.(*DemuxFlattener).End  
0 0.00% 99.57% 0.06s 2.60% github.com/golang/freetype/raster.(*Rasterizer).Add1  
0 0.00% 99.57% 0.06s 2.60% github.com/llgcode/draw2d/draw2dimg.FtLineBuilder.LineTo  
0 0.00% 99.57% 2.30s 99.57% github.com/usedbytes/drawbench.BenchmarkDraw2D  
0 0.00% 99.57% 2.30s 99.57% testing.(*B).launch  
0 0.00% 99.57% 2.30s 99.57% testing.(*B).runN  
0 0.00% 99.57% 0.03s 1.30% github.com/llgcode/draw2d/draw2dbase.DemuxFlattener.End

Collected using the testing package's built-in profiling:

$ go test -cpuprofile cpu.prof -bench .                                                                                                                                                                            
goos: linux                                                                                                                                                                                                        
goarch: amd64                                                                                                                                                                                                      
pkg: github.com/usedbytes/drawbench                                                                                                                                                                                
BenchmarkDraw2D-4           1000           2092810 ns/op                                                                                                                                                           
BenchmarkCairo-4           30000             44773 ns/op                                                                                                                                                           
PASS                                                                                                                                                                                                               
ok      github.com/usedbytes/drawbench  4.314s

$ go tool pprof -http=:8000 cpu.prof

The Cairo counterparts (cairo_stroke and cairo_fill) take a similar percentage of the time, but are faster in absolute terms. I imagine they're just much more highly optimised.

usedbytes avatar Sep 16 '18 21:09 usedbytes