Wizard API updated!
Tim Boudreau has released a new version of the Swing Wizard library (version 0.997) that fixes the WizardException bug reported in JavaWorld's recent Open Source Java Project profile. The article's examples have been reworked to test out the new, improved WizardException. Thanks, Tim, for this helpful fix!
Open Source Java Projects: The Wizard API

Newsletter sign-up

Sign up for our technology specific newsletters.

Enterprise Java
View all newsletters

Email Address:

Java performance programming, Part 2: The cost of casting

Reduce overhead and execution errors through type-safe code

For this second article in our series on Java performance, the focus shifts to casting -- what it is, what it costs, and how we can (sometimes) avoid it. This month, we start off with a quick review of the basics of classes, objects, and references, then follow up with a look at some hardcore performance figures (in a sidebar, so as not to offend the squeamish!) and guidelines on the types of operations that are most likely to give your Java Virtual Machine (JVM) indigestion. Finally, we finish off with an in-depth look at how we can avoid common class-structuring effects that can cause casting.

Java performance programming: Read the whole series!

Object and reference types in Java

Last month, we discussed the basic distinction between primitive types and objects in Java. Both the number of primitive types and the relationships between them (particularly conversions between types) are fixed by the language definition. Objects, on the other hand, are of unlimited types and may be related to any number of other types.

Each class definition in a Java program defines a new type of object. This includes all the classes from the Java libraries, so any given program may be using hundreds or even thousands of different types of objects. A few of these types are specified by the Java language definition as having certain special usages or handling (such as the use of java.lang.StringBuffer for java.lang.String concatenation operations). Aside from these few exceptions, however, all the types are treated basically the same by the Java compiler and the JVM used to execute the program.

If a class definition does not specify (by means of the extends clause in the class definition header) another class as a parent or superclass, it implicitly extends the java.lang.Object class. This means that every class ultimately extends java.lang.Object, either directly or via a sequence of one or more levels of parent classes.

Objects themselves are always instances of classes, and an object's type is the class of which it's an instance. In Java, we never deal directly with objects, though; we work with references to objects. For example, the line:

    java.awt.Component myComponent;


does not create an java.awt.Component object; it creates a reference variable of type java.lang.Component. Even though references have types just as objects do, there is not a precise match between reference and object types -- a reference value may be null, an object of the same type as the reference, or an object of any subclass (i.e., class descended from) the type of the reference. In this particular case, java.awt.Component is an abstract class, so we know that there can never be an object of the same type as our reference, but there can certainly be objects of subclasses of that reference type.

Polymorphism and casting

The type of a reference determines how the referenced object -- that is, the object that is the value of the reference -- can be used. For instance, in the example above, code using myComponent could invoke any of the methods defined by the class java.awt.Component, or any of its superclasses, on the referenced object.

However, the method actually executed by a call is determined not by the type of the reference itself, but rather by the type of the referenced object. This is the basic principle of polymorphism -- subclasses can override methods defined in the parent class in order to implement different behavior. In the case of our example variable, if the referenced object was actually an instance of java.awt.Button, the change in state resulting from a setLabel("Push Me") call would be different from that resulting if the referenced object were an instance of java.awt.Label.

Besides class definitions, Java programs also use interface definitions. The difference between an interface and a class is that an interface only specifies a set of behaviors (and, in some cases, constants), while a class defines an implementation. Since interfaces do not define implementations, objects can never be instances of an interface. They can, however, be instances of classes that implement an interface. References can be of interface types, in which case the referenced objects may be instances of any class that implements the interface (either directly or through some ancestor class).

Casting is used to convert between types -- between reference types in particular, for the type of casting operation in which we're interested here. Upcast operations (also called widening conversions in the Java Language Specification) convert a subclass reference to an ancestor class reference. This casting operation is normally automatic, since it's always safe and can be implemented directly by the compiler.

Downcast operations (also called narrowing conversions in the Java Language Specification) convert an ancestor class reference to a subclass reference. This casting operation creates execution overhead, since Java requires that the cast be checked at runtime to make sure that it's valid. If the referenced object is not an instance of either the target type for the cast or a subclass of that type, the attempted cast is not permitted and must throw a java.lang.ClassCastException.

The instanceof operator in Java allows you to determine whether or not a specific casting operation is permitted without actually attempting the operation. Since the performance cost of a check is much less than that of the exception generated by an unpermitted cast attempt, it's generally wise to use an instanceof test anytime you're not sure that the type of a reference is what you'd like it to be. Before doing so, however, you should make sure that you have a reasonable way of dealing with a reference of an unwanted type -- otherwise, you may as well just let the exception be thrown and handle it at a higher level in your code.

Casting caution to the winds

Casting allows the use of generic programming in Java, where code is written to work with all objects of classes descended from some base class (often java.lang.Object, for utility classes). However, the use of casting causes a unique set of problems. In the next section we'll look at the impact on performance, but let's first consider the effect on the code itself. Here's a sample using the generic java.lang.Vector collection class:

        private Vector someNumbers;
        ...
        public void doSomething() {
            ...
            int n = ...
            Integer number = (Integer) someNumbers.elementAt(n);
            ...
        }


This code presents potential problems in terms of clarity and maintainability. If someone other than the original developer were to modify the code at some point, he might reasonably think that he could add a java.lang.Double to the someNumbers collections, since this is a subclass of java.lang.Number. Everything would compile fine if he tried this, but at some indeterminate point in execution he'd likely get a java.lang.ClassCastException thrown when the attempted cast to a java.lang.Integer was executed for his added value.

The problem here is that the use of casting bypasses the safety checks built into the Java compiler; the programmer ends up hunting for errors during execution, since the compiler won't catch them. This is not disastrous in and of itself, but this type of usage error often hides quite cleverly while you're testing your code, only to reveal itself when the program is put into production.

Not surprisingly, support for a technique that would allow the compiler to detect this type of usage error is one of the more heavily requested enhancements to Java. There's a project now in progress in the Java Community Process that's investigating adding just this support: project number JSR-000014, Add Generic Types to the Java Programming Language (see the Resources section below for more details.) In the continuation of this article, coming next month, we'll look at this project in more detail and discuss both how it's likely to help and where it's likely to leave us wanting more.

1 | 2 |  Next >
Resources

Sidebar: Casting issues and JVM performance

Interfaces and method calls

Table 1 shows how different method-call types affect performance of several JVMs. The values in this table were obtained by running a test program that executes a small calculation (integer multiply and add) with various method-call wrappers. By comparing the times for the test execution using a particular type of method call with the basic calculation time, we can get an idea of the overhead involved in that type of method call.

Table 1. Method call performance (time in seconds; lower scores are better)
  JRE 1.1.8 (Sun) JRE 1.1.8 (IBM) JRE 1.2.2 (Classic) JRE 1.2.2 (HotSpot 2.0 beta)
1. Inline calculation

0.6

0.3

0.3

0.6

2. Called calculation method

0.9

1.7

0.9

0.8

3. Accessor method call

2.0

2.8

2.1

0.7

4. Overridden method call

3.2

3.7

3.2

5.6

5. Interface method call

5.5

5.2

5.5

6.5

6. Object allocation time

51.4

8.6

34.0

16.1

Line 1 gives the time for the calculation loop (executed 20 million times in this test) executed as a direct loop using a local variable, while line 2 gives the time for a simple method call with the initial value passed as a parameter and the result value returned. Given the simple nature of the calculation step, it's not surprising that adding a method call around it can slow the loop considerably. Only the IBM JVM shows a dramatic decrease in speed for this change, though, suggesting that it may not optimize this type of method call as well as the other alternatives.

Line 3 adds a twist to the simple method-call test of line 2, adding get and set method accesses to a variable in an object for each calculation step. This again slows the calculation, but not by a large amount.

These first three lines show almost identical times when using the HotSpot Server JVM, suggesting that it's converting the method calls into inline code. In the test on line 4, this breaks down, though. For this test, get and set methods that are overridden in a subclass are used. Even though the test uses the parent class implementation (and not the subclass with the overriding method defined), this form of call runs slower on all JVMs than calls to methods that are never overridden. The performance degradation is especially marked for the HotSpot version tested.

Line 5 shows the results of using an interface for the get and set method calls, rather than class-method calls. An interface method call requires more work by the JVM than a class-method call, and this shows in the test results. All JVMs tested ran this test slower than either a base or overridden class-method call, and, for all but HotSpot, the difference was substantial even compared to the slower overridden class-method call.

To offer some perspective on these results, the last line of the table shows the time taken to allocate the equivalent of one object (a java.lang.Integer instance, in this case) for each calculation step in the other tests. These times show that, although method-call overhead can slow performance considerably in high-usage code, its impact is in most cases much smaller than the object allocation overhead discussed in last month's article.

The costs of casting

JVMs use a variety of special techniques to minimize the overhead of the runtime checks required for downcasting, but there will always be situations where these techniques fail. Table 2 lists some results of an investigation into this issue with current JVMs, showing how various casting operations effect performance.

Table 2. Casting performance (time in seconds; lower scores are better)
  JRE 1.1.8 (Sun) JRE 1.1.8 (IBM) JRE 1.2.2 (Classic) JRE 1.2.2 (HotSpot 2.0 beta)
1. Method call

0.9

1.7

0.9

0.8

2. Direct member variable

1.2

1.2

0.7

0.7

3. Downcast member variable

2.4

2.2

2.4

1.3

4. Downcast parent member variable

5.2

2.2

8.7

1.3

5. Downcast method call

3.0

3.5

3.1

1.3

6. Downcast parent method call

5.9

3.4

9.6

1.3

7. Downcast parent overridden method call

7.5

4.1

10.3

6.0

8. Downcast to interface method call

9.6

5.6

9.7

7.0

9. Tested downcast to interface method call

9.7

6.2

9.9

7.7

10. Mixed cast parent method call

6.2

3.5

8.8

23.1

11. Object allocation time

51.4

8.6

34.0

16.1

Line 1 gives the time for the calculation step executed as a simple method call with the initial value passed as a parameter and the result value returned (the same as Line 2 in the prior table). These times are the basic comparison values for this set of test results, since all the other tests are build around this method call form.

Lines 2, 3, and 4 show different variations of accessing a member variable of an object. In line 2, the access is without casting. In line 3, an object reference is passed as a generic java.lang.Object and then downcast to the actual class of the object. In line 4, a generic object reference is passed and then downcast to a parent class of the object.

For these member-variable accesses, the adaptive JVMs (IBM JRE 1.1.8 and HotSpot) do a very good job of minimizing the overhead of a cast. In the last of these cases, where the cast is not to the actual class of the object, the standard JVMs (Sun JRE 1.2.2 and 1.1.8) are starting to show high levels of overhead for the cast operation.

Lines 5, 6, and 7 show different casting variations combined with method calls. In line 5, this is a simple downcast of a generic object reference to the actual class of the object. In line 6 the cast is to a parent class of the object, and in line 7 the method being called is one which is overridden by a loaded class. All the tested JVMs except HotSpot show fairly high overhead for the cast to the parent class of the object, and, for the case in which the called method is overridden, HotSpot shows fairly high overhead as well (though not nearly as high as the base Sun JRE 1.2.2).

Lines 8 and 9 show casting to an interface with method calls through the interface. These are again high-overhead operations, though less so for the adaptive JVMs. The difference between these two lines is a test before the cast in line 9, duplicating the common coding idiom of a check before a cast:

    if (obj instanceof IValue) {
        IValue iobj = (IValue) obj;
        ...
    }

This test was specifically designed to determine if the JVMs were able to translate this coding idiom efficiently. Since the cast operation is checked before it's executed, the hope was that there would be no need to check it again when actually performing the cast. Unfortunately, judging from the test times, it looks like the JVMs aren't able to make full use of this information in the generated code and do go through some duplicated effort. On the brighter side, however, the duplicated effort isn't as much as for the equivalent cast operation.

Line 10 shows the results of a test that used a different usage pattern. All the other tests used a single target type for the casts. In this test, two target types (one a subclass of the other) were used in alternation, with calls to a method inherited by both target types. This test only presented a problem for the HotSpot JVM, but in this case the problem was a substantial one, giving by far the worse performance of any of the tests.

It appears from these tests that part of HotSpot's performance advantage for casting operations is some type of caching of the cast last performed for an object. If the same cast is done repeatedly on an object, the performance is very good, but if a cast to another type is done, there's a large performance hit. This trade-off is probably good in general, but can result in unexpected performance bottlenecks when usage does not fit the expected pattern.

Finally, the last line of the table shows the time taken to allocate the equivalent of one object for each calculation step in the other tests. As with the Table 1 times for method call tests, these times show that casting is generally not as significant a performance factor as object allocations.