Faster alternatives to some Java intrinsic methods

# Faster alternatives to some Java intrinsic methods

One of my main interests when developing JPARSEC in the last months was to find faster alternatives to some of the (sometimes) slow intrinsic methods. Thanks to JVisualVM I identified the main bottlenecks inside JPARSEC and eliminated them, and at some time I was unable to improve performance because the main bottlenecks were inside Java itself. As I have written in a previous post the use of a OpenGL binding is not useful since it adds a lot of overhead and the resulting program is much slower (and unstable).

The main graphical bottleneck I've seen is the drawLine method of Java2D. Even when showing a straight line (that doesn't require antialiasing) it is extremely slow, and disabling antialiasing does not help. The best workaround I found is to use the PerfGraphics library by Werner Randelshofer. It contains very fast implementations for drawLine using the algorithms by Bresenham (without antialiasing) and Wu (with antialiasing). Alpha blending is not available or will slow down things, so what I do is a basic alpha blending of the color of the line with the background color, and use that color for the entire line. I also modified the method to support also basic strokes, including an slighly widther line for the Wu's algorithm to draw constellation figures. Since I use this for dragging the sky in the SkyChart component (releasing the mouse makes the high-quality image appear at the end) the speed increase worths the little image quality that it's lost. The Bressenham algorithm can be 5-10 times faster than Java2D's drawLine, and Wu's one around 3 times faster. Even when showing few lines the implementation in Java2D becomes a terrible bottleneck considering how fast is the rendering of the rest of things.

RenderSky and AWTGraphics (or AndroidGraphics) classes contains some workarounds to speed-up things. BufferedImages of type TYPE_INT_RGB (without alpha) are used, since I don't need alpha and it is already considered when blending colors using this type. drawImage is used whenever possible, even when showing stars as circles (never use fillOval!), and adjusting the image size to the star size as much as possible. drawOval is also replaced with an algorithm using Bressenham interpolation, including support for the basic stroke used to show the symbol of open clusters. fillRect is called after disabling antialiasing since it is faster. And the fast draw line algorithm is used whenever a given line is a straight line. The planet grid in RenderPlanet class was drawn using Java2D's drawLine (slow) or the fast draw line method in AWTGraphics (less quality), now it is rendered using the Wu's algorithm which seems an excellent method for both fast and good quality lines.

Maths are performed using FastMath preferently, a class that contains extremely fast implementations of trigonometric functions (interpolating in tables) and other math methods using the FastMath library by Bill Rossi. Data manipulation is done using the DataBase class, that now allows to return objects based on index instead of names, improving performance by 5%. Some minor bottlenecks appeared as inneficient/unnecesary use of methods like City.getCities or RenderPlanet.geodeticToGeocentric.

Another recent performance change was related to the Double.parseDouble and Float.parseFloat methods in Java. The tool 30mExplorer requires a large number of operations using these methods, and tests with JVisualVM showed a clear bottleneck here. After some tests I implemented the following piece of code to do the same, taking advantage of the very fast implementation of Integer.parseInt, compared to the other two. For certain numbers this implementation (in DataSet class) can be 20% slower than Java's one, but for real world use the performance boost is around a factor 2, which is quite noticeable in 30mExplorer.

	/**
* Parses a double from a String. Performance compared to intrinsic
* java method varies a lot: between 20% faster to 100% faster
* depending on the input values. The trick is to use Long.parseLong,
* which is extremely fast, or Integer.parseInt for the approximate
* but even faster mode (50% to 200%, 200% means 3 times faster).
* In case input string is a number with few digits this method is slower than
* java's one.
* @param s The string.
* @param approx True to allow an slight lose of precision, false
* for exact parsing.
* @return The double value.
*/
public static double parseDouble(String s, boolean approx) {
int exp = 0;
int e = s.indexOf("E");
if (e < 0) e = s.indexOf("e");
if (e >= 0) {
String ss = s.substring(e+1);
if (ss.startsWith("+")) ss = ss.substring(1);
exp = Short.parseShort(ss);
if (exp > 290 || exp < -290) return Double.parseDouble(s);
s = s.substring(0, e);
}
if (s.startsWith("+")) s = s.substring(1);
int p = s.indexOf(".");
int n = s.length();
if (p >= 0) {
exp -= (n - p - 1);
s = s.substring(0, p) + s.substring(p+1);
n = s.length();
}
if (n < 18 && exp < 0 && exp > -18 && s.endsWith("0")) { // this block is to return, for example, 1.0 when input string is 1.00000..., instead of 0.9999999999999...
String end = DataSet.repeatString("0", -exp);
if (s.endsWith(end)) {
s = s.substring(0, n + exp);
if (s.equals("")) return 0;
return Long.parseLong(s);
}
}

if (n < 9) return FastMath.multiplyBy10ToTheX(Integer.parseInt(s), exp);
if (approx) {
return FastMath.multiplyBy10ToTheX(Integer.parseInt(s.substring(0, 9)), exp + n - 9);
}
if (n < 18) return FastMath.multiplyBy10ToTheX(Long.parseLong(s), exp);
return FastMath.multiplyBy10ToTheX(Long.parseLong(s.substring(0, 18)), exp + n - 18);
}

/**
* Parses a double to a String. Performance compared to intrinsic
* java method varies a lot: between 20% faster to 100% faster
* depending on the input values. The trick is to use Long.parseLong,
* which is extremely fast.
* @param s The string.
* @return The double value.
*/
public static double parseDouble(String s) {
return DataSet.parseDouble(s, false);
}

/**
* Parses a float from a String. Performance compared to intrinsic
* java method varies a lot: between 20% faster to 400% faster
* depending on the input values. The trick is to use Integer.parseInt.
* This function calls {@linkplain #parseDouble(String, boolean)} using
* true for the flag 'approx'.
* @param s The string.
* @return The double value.
*/
public static float parseFloat(String s) {
return (float) DataSet.parseDouble(s, true);
}

Thanks to these efforts tools like DataCube and 30mExplorer are now much faster. The SkyChart component is now running beyond 125 fps using certain window size and rendering the region around Cygnus, including constellation lines with antialiasing. At the beginning performance was around 15-20 fps (even after considerable effort on performance, see some of the old posts), but now it is 8 times faster. Android implementation runs smooth, almost as fast as OpenGL based planetariums, despite a specific profiling in this platform is still pending. I must emphasize that the Android port was possible because memory footprint was reduced by a similar factor, also thanks to JVisualVM.

As conclusion, JVisualVM is an extremely useful tool not only to improve performance, but also to reduce memory footprint in Java aplications. Performance optimizations can go beyond what Java2D provides, and there's no reason why a planetarium in Java cannot perform faster than others in C++, while maintaining a comparable or better visual quality. A good example is Cartes du Ciel, which is (at least on Linux) extremely slow and with poor visual quality.

Respect next developments in JPARSEC, there's some important lights in the horizon. The potential of JPARSEC for scientific research will continue to be 'upgraded' in the next months, as has happend in the latest release. I will integrate ImageJ and other tools inside JPARSEC for optical data reduction. Telescope control is also planned and I will work on this during the summer. Everything cannot be made in parallel, so maybe the Android project will have to wait a little …

## Discussion

blog/java2d_fast_alternatives.txt · created: 2013/06/03 13:07 (Last modified 2016/05/20 15:58) by Tomás Alonso Albi