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();
}
}