Design for performance, Part 2: Reduce object creation
Avoid performance hazards while designing Java classes
Though many programmers put off performance management until late in the development process, performance considerations should be integrated into the design cycle from day one. This series explores some of the ways in which early design decisions can significantly affect application performance. In this article, I continue to explore the problem of excessive temporary object creation and offer several proven techniques for avoiding their creation.
Read the whole “Design for Performance” series:
- Part 1: Interfaces matter
- Part 2: Reduce object creation
- Part 3: Remote interfaces
Temporary objects are those that have a short lifetime and generally serve no useful purpose other than to act as containers for other data. Programmers generally use temporary objects to pass compound data to — or return it from — a method. Part 1 explored how temporary object creations could have a serious negative impact on a program’s performance and showed how certain class interface design decisions virtually guarantee temporary object creation. By avoiding those interface constructs, you can dramatically reduce the need to create temporary objects that can sap your program’s performance.
Just say no to String?
When it comes to creating temporary objects, the String
class is one of the biggest offenders. To illustrate that, in Part 1 I developed an example of a regular expression-matching class and showed how a harmless-looking interface imposed enough object-creation overhead to make it run several times slower than a similar class with a more carefully designed interface. Here are the interfaces for both the original and the better-performing classes:
BadRegExpMatcher
public class BadRegExpMatcher {
public BadRegExpMatcher(String regExp);
/** Attempts to match the specified regular expression against the input
text, returning the matched text if possible or null if not */
public String match(String inputText);
}
BetterRegExpMatcher
class BetterRegExpMatcher {
public BetterRegExpMatcher(...);
/** Provide matchers for multiple formats of input -- String,
character array, and subset of character array. Return -1 if no
match was made; return offset of match start if a match was
made. */
public int match(String inputText);
public int match(char[] inputText);
public int match(char[] inputText, int offset, int length);
/** If a match was made, returns the length of the match; between
the offset and the length, the caller should be able to
reconstruct the match text from the offset and length */
public int getMatchLength();
/** Convenience routine to get the match string, in the event the
caller happens to wants a String */
public String getMatchText();
}
Programs that heavily use BadRegExpMatcher
will run slower than those using BetterRegExpMatcher
. First, callers have to create a String
object to pass into match()
, which then has to create another String
object to return the matched text to the caller. That results in at least two object creations per invocation, which may not sound like much, but if you call match()
frequently, the performance overhead of those object creations can really add up. The problem with BadRegExpMatcher
‘s performance is not in its implementation but in its interface; with the interface defined as it is, there is no way to avoid creating several temporary objects.
BetterRegExpMatcher
replaces the String
objects in match()
with primitive types (integers and character arrays); no intermediate objects need to be created to pass information from the caller to match()
and back.
Since it is generally easier to avoid performance problems at design time than to fix them after you’ve written your entire program, you should watch out for the ways in which your class interfaces might mandate object creation. In the case of RegExpMatcher
, the fact that its methods require and return String
objects should provide a warning sign for potential performance hazards. Since the String
class is immutable, all but the most trivial processing of a String
argument will require the creation of a new String
for every invocation.
Is immutability necessarily bad for performance?
Because String
is so commonly associated with excessive object creation, which is generally ascribed to its immutability, many programmers assume that immutable objects are inherently bad for performance. However, the truth is somewhat more complicated. Actually, immutability can sometimes offer a performance advantage, and mutable objects can sometimes cause performance problems. Whether mutability is helpful or harmful for performance depends on how the object is used.
Programs frequently manipulate and modify text strings — which is a bad match for immutability. Every time you want to manipulate a String
— like finding and extracting a prefix or substring, converting it to upper- or lowercase, or combining two strings into a new one — you must create a new String
object. (And in the case of concatenation, the compiler creates a hidden temporary StringBuffer
object, too.)
On the other hand, a reference to an immutable object can be freely shared without having to worry that the referenced object will be modified, which can offer a performance advantage over mutable objects, as the next section illustrates.
Mutable objects have their own temporary object problems
In the RegExpMatcher
example, you saw that when a method had a return type of String
, it usually necessitated the creation of a new String
object. One of the problems with BadRegExpMatcher
was that match()
returned an object rather than a primitive type — but just because a method returns an object, doesn’t mean that a new object must be created. Consider the geometry classes in java.awt
such as Point
and Rectangle
. A Rectangle
is just a container of four integers (x, y, width, and height). The AWT Component
class stores the component location and returns it as a Rectangle
through the getBounds()
accessor method:
public class Component {
...
public Rectangle getBounds();
}
In the example above, the getBounds()
method is really an accessor — it simply makes available some state information that is internal to Component
. Does getBounds()
have to create the Rectangle
it will return? Maybe. Consider this possible implementation of getBounds()
:
public class Component {
...
protected Rectangle myBounds;
public Rectangle getBounds() { return myBounds; }
}
When a caller calls getBounds()
in the above example, no new objects are created — since the component already knows where it is — so getBounds
is quite efficient. However, Rectangle
‘s mutability creates other problems. What happens when a caller executes the following?
Rectangle r = component.getBounds();
...
r.height *= 2;
Because Rectangle
is mutable, it causes the component to move without Component
‘s knowledge. For a GUI toolkit such as AWT, that would be a disaster because, when a component is moved, the screen needs to be redrawn, event-listeners need to be notified, etc. So the code above seems a dangerous way to implement Component.getBounds()
. A safer implementation of Component
would implement getBounds()
as follows:
public Rectangle getBounds() {
return new Rectangle(myBounds.x, myBounds.y,
myBounds.height, myBounds.width);
}
But now, each call to getBounds()
creates a new object, just as RegExpMatcher
did. In fact, the following code fragment creates four temporary objects:
int x = component.getBounds().x;
int y = component.getBounds().y;
int h = component.getBounds().height;
int w = component.getBounds().width;
In the case of String
, the object creations were required because String
is immutable. But in this case, an object creation seems to be required because Rectangle
is mutable. We avoided the problem with String
by not using any objects in our interfaces. While that worked in the case of RegExpMatcher
, that solution is not always possible or desirable. Fortunately, you can employ several techniques when designing classes that allow you to rid yourself of the too-many-small-objects problem without avoiding small objects altogether.
Object-reduction technique 1: Add finer-grained accessor functions
In initial versions of the Swing toolkit, the creation of many temporary Point
, Rectangle
, and Dimension
objects seriously hampered performance. While it appeared more efficient to return multiple values at once by containing them in a Point
or Rectangle
, the object creation was, in fact, more expensive than multiple method calls. Before the final release of Swing, that problem was ameliorated quite simply by adding some new accessor methods to Component
and other classes, such as the following:
public int getX() { return myBounds.x; }
public int getY() { return myBounds.y; }
public int getHeight() { return myBounds.height; }
public int getWidth() { return myBounds.width; }
Now a caller can retrieve the bounds with no object creations such as this:
int x = component.getX();
int y = component.getY();
int h = component.getHeight();
int w = component.getWidth();
The old form of getBounds()
is still supported; the finer-grained accessor functions simply provide a more efficient way to complete the same objective. In effect, the interface of Rectangle
has been fully exposed in the Component
interface. When the Swing package was modified to support and use those finer-grained accessor functions, it resulted in many Swing operations performing nearly twice as fast as they had previously. That was significant, as GUI code is highly performance-critical — the user waits for something to happen and indeed expects UI operations to be instantaneous.
The downside of using that technique is that your objects now have more methods and more than one way to retrieve the same information, which makes the documentation larger and more complicated, and may discourage users. But as the Swing example showed, in performance-critical situations, that optimization technique can be effective.
Technique 2: Exploit mutability
In addition to adding primitive-valued accessor functions to Component
— like the getX()
function discussed above — Java 2 also uses another technique to reduce object creations in AWT and Swing. An alternate version of getBounds()
was added to Component
and other UI classes, which allows a caller to retrieve the bounds as a Rectangle
, but without requiring any temporary object creation:
public Rectangle getBounds(Rectangle returnVal) {
returnVal.x = myBounds.x;
returnVal.y = myBounds.y;
returnVal.height = myBounds.height;
returnVal.width = myBounds.width;
return returnVal;
}
The caller still must create a Rectangle
object, but it can be reused for subsequent calls. If a caller were iterating through a collection of Component
objects, it could create a single Rectangle
object and reuse it for each Component
. Note that the technique only works with mutable object types; you would not be able to eliminate String
creations in that way.
Technique 3: Get the best of both worlds
A more elegant way to solve the object-creation problem for simple classes such as Point
would be to make the Point
class immutable, but define a mutable subclass, as in the following example:
public class Point {
protected int x, y;
public Point(int x, int y) { this.x = x; this.y = y; }
public final int getX() { return x; }
public final int getY() { return y; }
}
public class MutablePoint extends Point {
public final void setX(int x) { this.x = x; }
public final void setY(int y) { this.y = y; }
}
public class Shape {
private MutablePoint myLocation;
public Shape(int x, int y) { myLocation = new MutablePoint(x, y); }
public Point getLocation() { return (Point) myLocation; }
}
In the example above, Shape
can return a reference to myLocation
safely because an attempt by the caller to modify the fields or call their setters will fail. (Of course, the caller could still cast the Point
to a MutablePoint
, but that is obviously an unsafe cast, and such callers would get what they deserve.) C++ programmers will note that the technique resembles C++’s ability to return a const
reference to a Rectangle
(const Rectangle&
) — a feature that Java does not have.
That technique — having a mutable and immutable version of a class that allows a read-only view of an object to be returned without creating a new object — is used in the java.math.BigInteger
class for the Java 1.3 class library. The MutableBigInteger
class is not exposed — it is a private type used internally by the java.math
class library. But since some of BigInteger
‘s methods (such as gcd()
) are actually composed of many arithmetic operations, performing the operations in place rather than creating hundreds of temporary objects results in a huge performance improvement.
Conclusion
As with all performance-tuning advice, it is worth remembering that there are many cases where a program’s performance is perfectly acceptable. In those cases, it is not worth sacrificing readability, maintainability, abstraction, or any other valuable program attribute to gain performance. However, since the seeds of many performance issues are sown at design time, it pays to be aware of the potential performance impact of certain design decisions. You can effectively employ the techniques provided here to reduce temporary object creation when you design classes that are likely to be used in performance-critical situations.
In Part 3, I’ll look at some of the performance issues specific to distributed applications and show how to spot and eliminate them at design time.