Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Performance is ~10-30x worse than Cairo #147

Open
usedbytes opened this issue Sep 13, 2018 · 3 comments
Open

Performance is ~10-30x worse than Cairo #147

usedbytes opened this issue Sep 13, 2018 · 3 comments

Comments

@usedbytes
Copy link

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.

@redstarcoder
Copy link
Contributor

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

@llgcode
Copy link
Owner

llgcode commented Sep 16, 2018

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

@usedbytes
Copy link
Author

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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants