- 282 - should always consider how a particular performance change will affect other parts of the
application. This means that tuning can be a lengthy process simply because it must be iterative. The full performance- tuning sequence identifying the bottleneck, tuning, and then benchmarking
is necessary to make sure that tuning one part of the application is not too detrimental to another part.
Performance tuning at the analysis and design phases differs from performance tuning at the implementation phase. Designing-in a performance problem usually results in a lot of trouble later
on, requiring a large effort to correct. On the other hand, coding that results in poor performance simply requires a tuning phase to eliminate bottlenecks, and is much simpler and cheaper to
correct. As a rule of thumb, a performance problem created or left uncorrected in one phase requires roughly five times as much effort to correct in the following development phase. Leaving
the problem uncorrected means that the effort required to correct it snowballs, growing fivefold through each development phase planning, analysis, schematic design, technical design,
construction, deployment, production.
[2] [2]
The fivefold increase is an average across the phases. Studies of the costs of fixing uncorrected problems have found that some phases have a higher cost than others.
Now on to the specifics. Before discussing when to optimize, Ill start with when you should not optimize.
13.1 When Not to Optimize
At the code-writing stage, your emphasis should not be on optimizing: it should be entirely on functionality and producing correct bug-free code. Apart from optimizations such as canonicalizing
objects that are good design, you should normally ignore performance while writing code. Performance tuning should be done after the code is functionally correct. Alan Knight
[3]
wrote:
[3]
Smalltalk Report, March-April 1996. This is a nice article about when and why to performance-tune.
If testing and documentation are inadequate, most people wont notice or care how fast a particular list box updates. Theyll have given up on the program before they ever got to that window.
This is definitely a view to which I subscribe. Many implementation-level optimizations can make code more complicated and difficult to read. Delay optimizing until the program is mostly
functionally correct. But make sure you have planned for a tuning phase.
I am not saying that you should create the whole application without considering performance until just before deployment. That would be foolhardy. Performance should be considered and planned
for at all phases of the development process especially design and architecture. You need to rule out designs that lead to a badly performing application. Optimizations that are good design should
be applied as early as possible. When parts of the application are complete, they should be tuned. And benchmarks should be run as soon as possible: they give a good idea of where you are and how
much effort will be needed for the tuning phase after code writing is mostly complete.
13.2 Tuning Class Libraries and Beans
Most code can be categorized into one of two general types:
- 283 -
•
Application -specific code, normally used for one particular application. If this code is reused, it usually provides only a skeleton that needs reimplementing. Occasionally,
application-specific code is generic enough to reuse in another application, but even then it usually needs some rewriting to make it more generic.
•
Classes written specifically with reusability in mind. This type of code is usually referred to as class libraries, frameworks, components, and beans. I refer to all of these together as
reusable code.
The first type of code, application-specific code, is considerably easier to tune. You can run the application as it is intended to be used, determine any bottlenecks, and successively tune away those
bottlenecks. Typically, 80 of the application time is spent in less than 20 of the code, and only 5 of the application code actually needs to be changed during the tuning process.
The second type of code, reusable code, is much more difficult to tune. This code may be used in many situations that could never be foreseen by the developers. Without knowing how the code will
be used, it is very difficult to create tests that appropriately determine the performance of reusable code. There is no truly valid technique that can be applied. Even exhaustively testing every method
is of little use not to mention generally impractical, since you almost certainly cannot identify useful performance targets for every method. Well-tuned reusable code can have 95 of the code
altered in some way by the tuning process.
[4] [4]
I have not seen any studies that show this cost. Instead, I base it on my own impression from examining early versions of various class libraries and comparing these classes with later versions. I find that most methods in a random selection of classes are altered in some way that I can identify as giving
performance improvements.
The standard way to tune reusable code is to tune in response to identified problems. Usually the development team releases alpha and beta versions to successively larger groups of testers: other
team developers, demo application developers, the quality-assurance team, identified beta testers, general beta testers, customers of the first released version some of these groups may overlap.
Each of these groups provides feedback in identifying both bugs and performance problems. In fact, as we all know, this feedback process continues throughout the lifetime of any reusable code. But
the majority of bugs and performance problems are identified by this initial list of users. This reactive process is hardly ideal, but any alternative makes tuning reusable code very expensive. This
is unlike bug testing, in which the quality of the test suite and quality-assessment process makes a big difference to the reliability of the released version, and is fully cost-effective.
There are several consequences to this reactive process. First, from the viewpoint of the developer using reusable components, you need to be aware that first versions frequently have suboptimal
performance. Note that this does not imply anything about the quality of the software: it may have wonderfully comprehensive features and be delightfully bug-free. But even in a large beta testing
program with plenty of feedback, there is unlikely to be sufficient time to tune the software and repeat the test and release procedures. Getting rid of identified bugs rightfully takes precedence, and
developers normally focus on the next released version being as bug-free as possible.
Second, for developers creating reusable code, the variety of applications testing the reusable code is more important than the absolute number of those applications. Ten people telling you that
method X is slow is not as useful as two telling you that method X is slow and two telling you that method Y is slow.
A further consequence when developing reusable code is that to provide greater performance flexibility for the users of those classes, you need to design more flexible method entry points to
your classes. Providing performance flexibility unfortunately clashes with the defensive programming that is reasonably used when creating reusable classes. For example, a defensive
- 284 - developer creating a collection class based on an array e.g.,
java.util.Vector
might provide a constructor that accepts an array and copies its elements:
public class ArrayBasedCollection {
int arraySize; Object[] array;
public ArrayBasedCollectionObject[] passedArray {
arraySize = passedArray.length; array = new Object[arraySize];
System.arraycopypassedArray, 0, array, 0, arraySize; }
...
The defensive developer always ensures that elements are copied into a new array so that no external object retains a reference to the internal array and interrupts the logic of the class. This
ensures that the new class cannot be inadvertently corrupted . However, this provides inefficient performance. There will be cases when the application coder has created the array specifically to
hold the objects, and will discard that array immediately. Developing flexibly with performance in mind directs you to add an extra method that allows the array to be used directly:
public class ArrayBasedCollection {
int arraySize; Object[] array;
public ArrayBasedCollectionObject[] passedArray {
thispassedArray, true; }
If copy is true, the elements of passedArray are copied into the a new underlying array in the collection.
If copy is false, the passedArray is assigned directly as the underlying array. This is potentially dangerous:
the collection object can be corrupted if the passedArray is altered directly by another object afterwards.
public ArrayBasedCollectionObject[] passedArray, boolean copy {
arraySize = passedArray.length; if copy
{ array = new Object[arraySize];
System.arraycopypassedArray, 0, array, 0, arraySize; }
else array = passedArray;
} ...
This opens the collection object to potential corruption, but by retaining the original one-arg constructor, you have reduced the chance that the two-arg constructor will be used accidentally. A
developer looking quickly for a constructor is likely to use the one-arg constructor, whereas a developer desperately searching through the documentation for ways to reduce the number of
copies made from several large arrays will be delighted to discover the two-arg constructor.
Finally, perhaps the most significant way to create reusable code that performs well is for developers to be well-versed in performance tuning. After any significant amount of performance
- 285 - tuning, many of the techniques in this book can become second nature. Developers experienced in
performance tuning can produce reusable code that is further along the performance curve right from the first cut. Writing reusable code is one of the few situations in which it is sometimes
preferable to consider performance when first writing the code.
13.3 Analysis