1.3 System Limitations and What to Tune
Three
resources limit all applications:
When tuning an application, the first step is to determine which of
these is causing your application to run too slowly.
If your application is
CPU-bound,
you need to concentrate your efforts on the code, looking for
bottlenecks, inefficient algorithms, too many short-lived objects
(object creation and garbage collection are CPU-intensive
operations), and other problems, which I will cover in this book.
If your application is hitting system-memory
limits,
it may be paging sections in and out of main memory. In this case,
the problem may be caused by too many objects, or even just a few
large objects, being erroneously held in memory; by too many large
arrays being allocated (frequently used in buffered applications); or
by the design of the application, which may need to be reexamined to
reduce its running memory footprint.
On the other hand,
external
data access or writing to the disk can be slowing your application.
In this case, you need to look at exactly what you are doing to the
disks that is slowing the application: first identify the operations,
then determine the problems, and finally eliminate or change these to
improve the situation.
For example, one program I know of went through web server logs and
did reverse lookups on the IP addresses. The first version of this
program was very slow. A simple analysis of the activity being
performed determined that the major time component of the reverse
lookup operation was a network query. These network queries do not
have to be done sequentially. Consequently, the second version of the
program simply multithreaded the lookups to work in parallel, making
multiple network queries simultaneously, and was much, much faster.
In this book we look at the causes of bad performance. Identifying
the causes of your performance problems is an essential first step to
solving those problems. There is no point in extensively tuning the
disk-accessing component of an application because we all know that
"disk access is much slower than memory
access" when, in fact, the application is CPU-bound.
Once you have tuned the application's first
bottleneck, there may be (and typically is) another problem, causing
another bottleneck. This process often continues over several tuning
iterations. It is not uncommon for an application to have its initial
"memory hog" problems solved, only
to become disk-bound, and then in turn CPU-bound when the disk-access
problem is fixed. After all, the application has to be limited by
something, or it would take no time at all to run.
Because this bottleneck-switching sequence is normal—once
you've solved the existing bottleneck, a previously
hidden or less important one appears—you should attempt to
solve only the main bottlenecks in an
application at any one time. This may seem obvious, but I frequently
encounter teams that tackle the main identified problem, and then
instead of finding the next real problem, start applying the same fix
everywhere they can in the application.
One application I know of had a severe disk I/O problem caused by
using unbuffered streams (all disk I/O was done byte by byte, which
led to awful performance). After fixing this, some members of the
programming team decided to start applying buffering everywhere they
could, instead of establishing where the next bottleneck was. In
fact, the next bottleneck was in a data-conversion section of the
application that was using inefficient conversion methods, causing
too many temporary objects and hogging the CPU. Rather than
addressing and solving this bottleneck, they instead created a large
memory-allocation problem by throwing an excessive number of buffers
into the application.
|