Monday, October 11, 2010

JCaptcha, SecureRandom & performance

Personally, I'm not big fan of JCaptcha library and recently was lucky enough to find another problem there. The problem is related mostly to linux and it's implementation of java.security. SecureRandom, which is known to be slow and is locking on every call to it.

For some reason (I suspect that this was the reason) JCaptacha overuse java.security.SecureRandom when it generates background image using FunkyBackgroundGenerator. Number of calls to get next random float can easily reach something around 100 000 per one captcha image. It is basically called at least once for each pixel.

Lets run some quick tests to have a look how bad is that. I have wrote write simple captch engine:


public static class MyImageCaptchaEngine extends ListImageCaptchaEngine {
protected void buildInitialFactories() {
WordGenerator wgen = new RandomWordGenerator("ABCDEFGHIJKLMNOPQRSTUVWXYZ123456789");
RandomRangeColorGenerator cgen = new RandomRangeColorGenerator(
new int[] {0, 100},
new int[] {0, 100},
new int[] {0, 100});
TextPaster textPaster = new RandomTextPaster(new Integer(7), new Integer(7), cgen, true);

BackgroundGenerator backgroundGenerator = new FunkyBackgroundGenerator(new Integer(200), new Integer(100));

Font[] fontsList = new Font[] {
new Font("Arial", 0, 10),
new Font("Tahoma", 0, 10),
new Font("Verdana", 0, 10),
};

FontGenerator fontGenerator = new RandomFontGenerator(new Integer(20), new Integer(35), fontsList);

WordToImage wordToImage = new ComposedWordToImage(fontGenerator, backgroundGenerator, textPaster);
this.addFactory(new GimpyFactory(wgen, wordToImage));
}
}
And the test itself:



long begin = System.currentTimeMillis();
for(int i = 0; i != 100; ++i) {
engine.getNextCaptcha();
}
long end = System.currentTimeMillis();
System.out.println("Total time is [" + (end - begin) + "]");
now lets run it:

Total time is [10967]

Ok, so what we have now it 10967ms, which, I believe, is bad. It can be significantly improved. I'm not very big fan of high-quality random backgrounds, so I will replace SecureRandom class, used by parent of FunkyBackgroundGenerator with "pseudo" random Random class. Funs of high-quality random backgrounds can still use SecureRandom for seeding, through:


public static class MyFunkyBackgroundGenerator extends FunkyBackgroundGenerator {
public MyFunkyBackgroundGenerator(Integer width, Integer height) {
super(width, height);
try {
Field rndField = AbstractBackgroundGenerator.class.getDeclaredField("myRandom");
rndField.setAccessible(true);
rndField.set(this, new Random());
}
catch (Exception e) {
e.printStackTrace();
}
}
}
I know, that is dirty hack, but as far "myRandom" is declared with default visibility, that's the shortest way to replace it for now . And what we have now is:

Total time is [1308]

Approximately 7 time quicker. Not that bad, specially for a sample case. In real world application improvement will be even more significant because some other processes may use '/dev/(u)random' or application itself can utilize SecureRandom for other purposes.

That's not it. There is another bottleneck, which is usage of Java2D for rendering captcha images. Java2D is well known for it's problems with multi-threading and the summary of these problems is that Java2D doesn't scale well. Some details can be found here. Possibly the way to fix that problem may be removing Java2D and using direct access to image via WritableRaster instead. However, it doesn't solve all problems, as far as Java2D is still used for drawing text.

2 comments:

Maverick.Crank.GRey said...

Stas, I have missed a round you select CAPTCHA engine. But could you remind me please, why http://www.google.com/recaptcha is not used on your site?

Stas said...

Just history. That captcha there is for years now.
Also, Personally, I do not feel well to let google employing our customers to recognize books for them :)