Writing optimized code is not something taught in any standard course on computer programming. Neither is it something anybody will learn in practice unless they put in special effort to understand this complex and dynamic field. Most people however would have come across this aspect during their professional careers, as the real world forces us to sometimes give up nice theoretical blueprints and patterns.
Note that I am not implying blueprints, patterns and theoretical guidelines are bad. On the contrary, used sensibly these artifacts can greatly reduce software complexity and increase reliability. However, sometimes theory does not meet reality and pure developers get lost, even though they have good intentions. A very good example of this is the highly debated Sun's J2EE Blueprint vs. Microsoft's .NET recourse. Why is Microsoft's solution so much faster? Simply because they have sacrificed some of the theoretical elegance of Sun's solution in order to gain performance. Performance is more often than not a critical success factor for any major product.
Imagine an eCommerce enabled web site on which you can purchase books. You log on to the site and start browsing for books you are interested in. Unfortunately because the site is currently carrying a thousand concurrent users (they have a big book sale going), it takes approximately three minutes to return a search result, and one out of three times the request times out. Unless the book you are looking for is an extremely rare book, you will most likely go to Amazon or someone else instead that will provide quicker response times. The essence of this story is that if the performance of your product suffers, and performance is an important aspect of your system, you will loose revenue.
Now that you understand performance is critical to the success of your system, I would like to introduce some basic aspects of optimizing source code. As I mentioned earlier, this is a very complex and involved topic, therefore I will only provide an introduction in this article.
Lets consider a very obvious, though regularly neglected aspect of code optimization. Consider the following method.
Entry getEntry() {
if (lookupContext("SomeContext") == NULL)
createContext();
Entry vEntry = NULL;
if (lookupEntry("SomeContext/Entry") == NULL) {
vEntry = new Entry("Param1");
bindEntry("SomeContext/Entry",vEntry);
}
else
vEntry = lookupEntry("SomeContext/Entry");
return vEntry;
}
The idea behind this code is to lookup an Entry
object in a directory, and if it (or it's context)
cannot be found, to create it and bind the new Entry
object to it. This code is functionally
correct, however performance wise it is pathetic. This code typically would only once bind the
Entry
object to the directory, and all the other times retrieve the existing object from the
directory. As we have a typical 99% vs. 1% scenario, it makes sense to optimize the aforementioned code for the
code that is executed 99% of the time, and not the code that is only executed 1% of the time.
Analyzing the previous method, and assuming it is called for the first time, the lookupContext()
call will return NULL
and the context will thus be created by the createContext()
call. Next the lookupEntry()
call will fail with a NULL
result, and the bindEntry()
call will bind the newly created object to the directory. This totals to four highly expensive method calls,
assuming it is expensive to communicate with the (possibly remote) directory server.
Analyzing the same method, but for the second and consecutive times it is called, the
lookupContext()
call will return a valid value, then the lookupEntry()
call will also
return a valid value, and lastly cause the else clause to be executed containing the lookupEntry()
call that returns the actual Entry
object. This is three expensive calls, a bit better than the
previous scenario but still too expensive. Reordering the code blocks in that method, yielding the same
functionality but improved performance gives the following result.
Entry getEntry() {
Entry vEntry = NULL;
if ((vEntry = lookupEntry("SomeContext/Entry")) == NULL) {
if (lookupContext("SomeContext") == NULL)
createContext();
vEntry = new Entry("Param1");
bindEntry("SomeContext/Entry",vEntry);
}
return vEntry;
}
Armed with the knowledge of the 99% vs. 1% principle, we optimized the code so that for the initial invocation, still four expensive calls are made, but for the second and consecutive invocations only one expensive call is made. This is an improvement in performance by a factor of 3, or in percentage by 300%. Imagine this method being called 500 times per minute - not unrealistic for typical J2EE applications - and assume an average duration of 10 milliseconds per expensive method call. This will cause the cumulative duration of 15 seconds in a one minute window for the old (unoptimized) method, and a cumulative duration of 5 seconds for the optimized method.
The lesson to be learned here is that you should spend your time optimizing the 20% of the code that gets called 80% of the time. Most of the time a small portion of the code gets called very frequently, and optimizations done here will be the most visible. Do not waste time or energy optimizing code that will only execute once or twice during the lifetime of the application, only to save 100 milliseconds in startup time. Rather spend that effort on code in the critical path of execution through your system.
Unnecessary method invocations should be avoided at all costs. An example would help illustrate this.
class Client {
CompanytList mCompanies;
public CompanyList getCompanies();
}
class Company {
String mName;
public String getName();
}
class SomeClass {
ClientList mClients;
public getCompanyName() {
if (mClients.getByName("Client1").getCompanies().getByName("Company1")
.getName() != NULL &&
!mClients.getByName("Client1").getCompanies().getByName("Company1")
.getName().equals(""))
return mClients.getByName("Client1").getCompanies().getByName("Company1")
.getName();
}
}
The problem with this piece of code is that assuming the getCompanies()
method needs to retrieve the
data from a persistent datastore each time it is invoked, we now have three invocations hence three datastore
roundtrips. Furthermore, each time a getByName()
method is invoked, it needs to search through a
(possibly unsorted) list to retrieve the correct object. This can also be costly. A better way to write this
code is illustrated below.
class Client {
CompanytList mCompanies;
public CompanyList getCompanies();
}
class Company {
String mName;
public String getName();
}
class SomeClass {
ClientList mClients;
public getCompanyName() {
String vName = mClients.getByName("Client1").getCompanies()
.getByName("Company1").getName();
if (vName != NULL && !vName.equals(""))
return vName;
}
}
Can you see that now we only have one invocation of each expensive method? This dramatically increases the performance of the method without suffering functionality loss. It even improves the readability of the code by a big factor, hence the reliability and maintainability increases.