This chapter introduces a performance process that is based on standard object-oriented software development methodologies. This process includes the standard phases of a software development cycle and adds one additional phase, performance profiling.
If you're interested in learning more about object-oriented design (OOD) and the overall software development process, the resources listed in Section 2.2 are a good place to start.
The basic OOSD process has four main phases:
Figure 2-1 shows the steps in the performance process. These mirror the phases of the traditional OOSD model, with one addition. This new phase is performance profiling-determining the performance characteristics of a software system.The performance process
Popular OOSD methodologies define particular tasks to be completed during each development phase. As we discuss each phase, we'll introduce some additional performance-related tasks that are critical to producing high-performance software. These complement, rather than replace, those required by the OOSD methodologies.
Different methodologies specify different deliverables for the analysis phase. One of these is usually a software requirements specification (SRS). The SRS specifies exactly what your software is supposed to do. It generally includes a high-level analysis of the problem domain and a set of scenarios that illustrate key functionality. This information is required input to the design phase of the project.
From a performance perspective, there are some key pieces of information that need to be included in the SRS. One key item that should be included in your SRS is a set of use cases. Use cases are descriptions of the sequence of events and actions that occur when a user interacts with the system. They are used to describe the outwardly visible requirements of a system.1 These descriptions can be textual, or might include diagrams. Use cases are an important step in defining the conceptual model of your system, and they are also crucial for creating testing plans and documentation. The major reason to be interested in them from a performance perspective, however, is benchmarking. In general, use cases can make an excellent way to determine what types of benchmarks you should create to measure the performance of your system. By basing your benchmarks on use cases, you can better ensure that your benchmarks are representative of the real-world use of your system. See Chapter 3, Measurement Is Everything, for more information about benchmarking.
In addition to the information about what the software is going to do, the SRS should specify planned limitations-what your product definitely will not do. What parts of the problem domain will the product not address? This information is very important-a product cannot be all things to all people. Systems that try to do everything generally fail. Well-defined boundaries on your software's scope can open up big optimization opportunities during the design and coding phases.
The SRS must also go beyond just software requirements; it needs to include system and performance requirements. System requirements include information such as:
Once you have your basic system requirements you can define specific performance requirements. Performance requirements are the most useful when they are quantifiable. For example:
Too often, projects move from analysis, to design, to coding, and on to beta testing before the engineering team knows what the performance requirements are. The team might have the system working well in the lab on a 500MHz machine, only to find that the target customer will be using a 200MHz machine. Before you start, engineering, management, marketing, and ideally your customers should reach a consensus about the performance requirements for your system. If you don't, how will you know when your product is fast enough?
Encapsulation encourages you to isolate the internal structure of your objects from the rest of your program. Generally, encapsulation is discussed in the context of improved software maintenance-not improved performance. In fact, there is a general perception among some programmers that encapsulation hurts performance by requiring greater data indirection and more method calls. While it is true that the increased levels of indirection lead to some slowdowns when compared to theoretical maximum performance levels, encapsulation is essential in large, scalable, high-performance systems.
Making encapsulation part of your design from the start enables you to:
List. The
framework also provides several implementations of this abstraction including
LinkedList and ArrayList. These are all discussed in greater detail in Chapter
8, Algorithms and Data Structures, but for now all you need to know is that these
two classes have radically different performance characteristics even though they
implement the same interface. Neither class is the fastest for all tasks-either can
be several times faster than the other, depending on the types of operations that are
performed on the List.
The increased indirection caused by the encapsulation of the list data-structures does mean that more method invocations are used to carry out operations than if the internal structures were exposed. However, this is a very small performance decrease-in typical cases, only a few percent. Contrast this with the possibility that switching from an array-based list to a linked list might result in a tenfold speedup of a critical operation in your system. If you expose the raw data structures everywhere in your program, you might have to modify hundreds of lines of code just to find out which solution is fastest. On the other hand, if you use encapsulation, you might only need to change one line of code to try the alternative solution.
A similar case is found in the Swing3 model classes. Chapter 10, Swing Models and Renderers, shows how the use of encapsulation makes it possible for components such as JTable to scale to accommodate huge data sets.
These cases both show how the use of encapsulation inside the Java libraries is important. However, it is equally important to apply these concepts when designing your own classes.
Using encapsulation makes it easier to adapt to changing requirements. This is obviously important for system maintenance, but it is also critical for system performance. For example, if your initial SRS states that your system has to be able to handle up to 100 users. you might decide to store user data in flat files. If the system is so well-received during beta testing that management decides to roll it out companywide to 10,000 users, your flat-file system is probably going to become a performance bottleneck.
To eliminate the bottleneck, you need to rework the system to use an industrial-strength database to store user data. If you encapsulated your data-storage mechanism inside a set of well-designed abstract classes, moving from the flat-file structure to the database should be fairly easy. However, if the storage mechanism isn't well encapsulated and many parts of your system assume that the user data is stored in files, the rearchitecture could take a significant amount of development time.
By analyzing data from a profiler, you can isolate the parts of the system that are causing your performance problems. This information can then be used to determine what changes will reap the greatest benefit. Sometimes the solution is as simple as modifying a single method, algorithm, or data structure. However, studying your system with a profiler can also reveal flaws in your OOD, and even in your original problem analysis. You need to be willing to revisit these early stages of the process. Grady Booch, a prominent object-oriented design expert, notes:
Rigid approaches to design only lead to largely useless design products that resemble a progression of lies, behind which developers shield themselves because no one is willing to admit that poor design decisions should be changed early, rather than late.4
There are several good commercial profiling tools on market, and a simple free tool is included with Sun's version of the Java 2 SDK. Without a profiling tool you'll be left guessing about what parts of your system need optimization, and you risk wasting a great deal of effort optimizing code that has nothing to do with the true bottlenecks. Profiling is discussed in depth in Chapter 3.
Booch, Grady. Object-Oriented Analysis and Design with Applications, Second Edition, Addison-Wesley, Reading, MA, 1994.
Gamma, Erich, Richard Helm, Ralph Johnson, and John Vlissides. Design Patterns: Elements of Reusable Object-Oriented Software, Addison-Wesley, Reading, MA, 1995.
Goldstein, Neal, and Jeff Alger. Developing Object-Oriented Software for the Macintosh: Analysis, Design, and Programming, Addison-Wesley, Reading, MA, 1992.
Grand, Mark. Patterns in Java, Volume 1, John Wiley & Sons, New York, 1998.
Larman, Craig. Applying UML and Patterns: An Introduction to Object-Oriented Analysis and Design, Prentice Hall, Upper Saddle River, NJ, 1998.
Schneider, Geri, and Jason Winters. Applying Use Cases: A Practical Guide, Addison-Wesley, Reading, MA, 1998.
For more information about use cases, see Geri Schneider and Jason Winters, Applying Use Cases: A Practical Guide, p. 1. Addison-Wesley, 1998.
2Craig Larman, Applying UML Patterns: An Introduction to Object-Oriented Analysis and Design, p. 499. Prentice Hall, 1998.
3Swing refers to the Java Foundation Classes (JFC) Swing packages, which provide a comprehensive set of classes for building graphical user interfaces.
4Grady Booch, Object-Oriented Analysis and Design with Applications. Addison-Wesley, 1994.
Copyright © 2001, Sun Microsystems,Inc.. All rights reserved.