views:

72

answers:

2

I'm trying to improve performance of captcha image rendering in web app running on Linux. Looking at what is currently used, I found that the bottleneck is in the usage of Java2D and specifically Graphics2D class.

The problem is not much with the speed of execution, but more with scalability. Basically it doesn't scale. Drawing of captcha images in 1 thread or 2 threads doesn't make any improvement in terms of execution time.

As an example, you can have a look at following class which is creating background for captcha images. The problem appears on calls to Graphics2D::setColor() and Graphics2D::drawLine():

http://www.docjar.com/html/api/com/octo/captcha/component/image/backgroundgenerator/FunkyBackgroundGenerator.java.html

After some googling and I found topic which says that Java2d is not particularly well with multi-threading (sorry, not allowed to give more than one link :) but, you can easily find that topic if google for 'java2d multithreading', it will be the first result)

I believe that there must be some library which provides drawing capabilities withtout using Java2d, but failed to find it :( Or Java2d, probably, can be switched to some mode, which doesn't block on access to graphics object (btw, headless mode doesn't help).

I will appreciate any suggestions. Beforehand, thanks for answers.

+1  A: 

There's not going to be a fast way to share a Graphics2D that works predictably because, unless you had a way to sync and reorder on each pixel, it'd be a massive race condition.

Anyway, your Graphics2D is backed by a BufferedImage so that's probably what's slowing you down. It's a non-accelerated surface so drawing is always going to be really slow. If your rendering server has the graphics hardware for it (it really should for an application like this) you can use a VolatileImage which is about an order of magnitude or two faster than a BufferedImage across the board in my experience.

Otherwise, you'll have to slice up your background generation into a grid, AffineTransform them so it all lines up, make the "randomness" common across all the grid elements by seeding it, stitch them back together afterwords and hope that the copyArea(...) method is fast enough to net you an improvement. I would almost say this is a kludge and hardware accelerated is the way to go.

You should also consider pre-rendering a large number of them offline and just serving them up as needed. That way the performance is more or less a non-issue unless you can't get in front of demand during the servers idle time (in which case you need new hardware either way and should just make a hardware accelerated rendering box).

j flemm
Thanks for answer and appreciate your advised. I will try to use them to make it slightly faster. But have a look at source code, I've gave a link to. These methods which are called on Graphics object are not doing anything apart from placing colored pixel at location. Accelerating such thing on graphics hardware sounds just laughable. Another point is that server is running on some blade server and I do not know where even geographically that box is located, it is even probably, not a physical box at all.
Stas
Ideal solution would be a library which can work with images without graphics card at all, but failed by find it :(
Stas
@Stas: VolatileImages will usually be noticeably faster than a BufferedImage even in the point-drawing case due to the fact it's stored in video ram and all draw calls are translated behind the scenes into hardware accelerated calls. Don't underestimate how fast modern GPUs really are especially if setColor() and drawLine() are your bottlenecks. You can also create a BufferedImage from an array of ints that represent your color values and see if that speeds things up. At the very least it should be trivial to parallelize.
j flemm
Hurrah, I got rid of graphics and replaced it with WritableRaster. Will mark your answer as THE answer on that question, because you've pushed me into idea to do that (array with color values). Thanks!
Stas
A: 

Some optimisation ideas based on a brief look at your code:

  • You're creating a new BufferedImage for every captcha. I think you are better off keeping one BufferedImage and Graphics2D per thread in ThreadLocal variables and drawing over the previous captcha whenever you create a new one
  • You are doing a big loop over every pixel with a lot of computation for each pixel. You want to absolutely minimise the calculation done in the middle of this loop, e.g. do the constant calculations of "colorRightDown.getRed() / 255.0f" etc. outside the loop
  • Consider converting all the floating point calculations to equivalent fixed-point integer maths. This is usually slightly faster providing you can make everything fit in ints.
  • Use BufferedImage.setRGB() with integer colour values rather than Graphics2D.setColor with a new Color - it will be much faster and save you a lot of GC pressure
  • See if you can reduce the number of random number calls per pixel, I count 7 per pixel.... can you get away with less than that? You may be better creating a random integer and testing subsets of the bits.
  • Use width rather than getImageWidth() in your inner (i) loop, otherwise you are calling getImageWidth unnecessarily for every pixel. Same for the (j) loop, although it matters much less.

My guess is that the above combined will gain you far more than throwing extra processors at the problem..... :-)

mikera