Hi, with all the talk of AWT performance and some talk about caching stroking, I did some benchmarking. (Attached) Basically, we suck :) All tests run on FC5 on an x86. Java 1.5.0: 0.47 seconds Java 1.4.2: 0.40 seconds (consistently faster!) JamVM/Classpath/Cairo 1.0.4: 7.9 seconds (ugh) Now, having implemented all the speedup ideas I have for this, (caching of path objects), the speedup was negligible[1]. The reason is simple, measuring the time spent in cairo_stroke() only, (benchmark not included, this was hacked into the C code of our CairoGraphics2D), we find that way over 95%, of the time here is spent in cairo_stroke(). And since cairo_fill() doesn't work significantly faster, my conclusion is that there's no workaround for a slow Cairo and there's no point in us trying to speed this up much more at the moment. Some results: Drawing to a Cairo surface (instead of an xlib one) was somewhat faster, around 5.8 seconds. Still very slow. Cairo 1.2 seemed a tad faster for cairo surface, about 5.7 s, for xlib surfaces there was no significant difference. [1] The things I tried were: * Caching PathIterator objects in the Shape (the benchmark uses GeneralPath, which does not cache its PIs in the current version). * Caching the cairo_path_t:s of various object, avoiding iterating over the path and calling into cairo every time. * Stroking the path ourselves with BasicStroke and and using cairo_fill() instead. * Caching the above. Again, none of this yields any significant speedups. So basically, let's go bug the Cairo guys. :) Supposedly they're going to work on making stuff faster now, so it'll be interesting to revisit these results later. /Sven
import java.awt.Frame; import java.awt.Graphics; import java.awt.Graphics2D; import java.awt.Color; import java.awt.geom.GeneralPath; import java.util.Random; public class StrokeBench extends Frame { private static final int NPATHS = 500; private static final int MAXSEGMENTS = 30; private static final long SEED = 1294; private static final int WINSIZE = 500; private static final int NITERATIONS = 10; private GeneralPath[] paths; private long totalTime; private double iterations; private Random r; private StrokeBench() { super("Stroke benchmark"); r = new Random(SEED); generatePaths(); // Random ID # to confirm we've used the same numbers. System.out.println("ID: "+r.nextInt()); setSize(WINSIZE, WINSIZE); totalTime = 0; setVisible(true); } private void generatePaths() { paths = new GeneralPath[ NPATHS ]; for(int i = 0; i < NPATHS; i++ ) { paths[i] = new GeneralPath(); int nSegs = r.nextInt(MAXSEGMENTS); paths[i].moveTo((float)(r.nextFloat() * WINSIZE), (float)(r.nextFloat() * WINSIZE)); for(int j = 0; j < nSegs; j++ ) addRandomSegment( paths[i] ); if( r.nextBoolean() ) paths[i].closePath(); } } private void addRandomSegment( GeneralPath path ) { int type = r.nextInt(3); switch(type) { case 0: path.lineTo((float)(r.nextFloat() * WINSIZE), (float)(r.nextFloat() * WINSIZE)); break; case 1: path.quadTo((float)(r.nextFloat() * WINSIZE), (float)(r.nextFloat() * WINSIZE), (float)(r.nextFloat() * WINSIZE), (float)(r.nextFloat() * WINSIZE)); break; case 2: path.curveTo((float)(r.nextFloat() * WINSIZE), (float)(r.nextFloat() * WINSIZE), (float)(r.nextFloat() * WINSIZE), (float)(r.nextFloat() * WINSIZE), (float)(r.nextFloat() * WINSIZE), (float)(r.nextFloat() * WINSIZE)); break; } } public void paint(Graphics gr) { Graphics2D g = (Graphics2D)gr; g.setColor(Color.white); g.fillRect(0, 0, WINSIZE, WINSIZE); g.setColor(Color.black); long start = System.currentTimeMillis(); for( int i = 0; i < NPATHS; i++ ) g.draw( paths[ i ] ); long time = System.currentTimeMillis() - start; totalTime += time; iterations += 1.0; System.out.println("Time: "+time+" ms\tTotal time: " +totalTime+" ms\t Average: "+ (((double)totalTime)/(1000*iterations))+" s"); if( iterations > NITERATIONS ) System.exit(0); repaint(); // Keep triggering repaints, quite nasty :) } public static void main(String[] args) { new StrokeBench(); } }