Skip to content

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

Open

Description

@usedbytes

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.

Activity

  1. redstarcoder commented on Sep 16, 2018

    @redstarcoder
    Contributor

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

  2. llgcode commented on Sep 16, 2018

    @llgcode
    Owner

    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

  3. usedbytes commented on Sep 16, 2018

    @usedbytes
    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

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions