Don’t Optimize Your Code (Yet)

More computing sins are committed in the name of efficiency (without necessarily achieving it) than for any other single reason – including blind stupidity.

-William A. Wulf

As developers, we build software and make it work.  Part of that is making it work well, which often means fast.  So sometimes we get into the (bad) habit of spending too much time optimizing some code that we’re writing, without first demonstrating that it has performance problems to begin with.

As an example, consider string concatenation using the + operator vs. StringBuilder.  It’s a pretty common idiom that if you’re building one large String from a bunch of smaller ones that you should use StringBuilder to combine them all together instead of using +, since + is so slow and StringBuilder is so fast.

Here is a simple POJO for a User.  I wrote the private field declarations by hand and then had Eclipse generate the constructor and toString() implementation for me.  Eclipse’s toString() uses string concatenation so I also wrote a toString2() method that uses StringBuilder:

public class User
{
    private String firstName;
    private String lastName;
    private String email;
    private String address;
    private String phone;

    public User(String firstName, String lastName, String email, String address, String phone)
    {
        super();
        this.firstName = firstName;
        this.lastName = lastName;
        this.email = email;
        this.address = address;
        this.phone = phone;
    }

    @Override
    public String toString()
    {
        return "User [address=" + address + ", email=" + email + ", firstName=" + firstName
                + ", lastName=" + lastName + ", phone=" + phone + "]";
    }

    public String toString2()
    {
        StringBuilder buffer = new StringBuilder("User [address=").append(address);
        buffer.append(", email=").append(email);
        buffer.append(", firstName=").append(firstName);
        buffer.append(", lastName=").append(lastName);
        buffer.append(", phone=").append(phone);
        buffer.append("]");
        return buffer.toString();
    }
}

Next I wrote a simple test that compares the running times of these two methods:

public class UserPerformanceTest
{
    public static void main(String[] args)
    {
        User user = new User("Zach", "Cox", "zach@somewhere.com", "123 Main St Somewhere CA",
                "(123) 555-1212");
        long t1 = System.nanoTime();
        user.toString();
        long t2 = System.nanoTime();
        double msec = (double) (t2 - t1) / 1000000d;
        System.out.println("toString() took " + msec + " msec to execute");

        long t3 = System.nanoTime();
        user.toString();
        long t4 = System.nanoTime();
        msec = (double) (t4 - t3) / 1000000d;
        System.out.println("toString2() took " + msec + " msec to execute");
    }
}

Based on a bunch of trials, this is pretty average output for this program:

toString() took 0.068999 msec to execute
toString2() took 0.015164 msec to execute

So using the + operator is about 4.5 times as slow as using StringBuilder, in this example. So what? Let’s say this was being used to display some user info in a web app. Do you really think spending 0.069 msec vs 0.015 msec building a string is going to be perceptible to the user? In case you think it will: it won’t!  The performance savings we got by spending all the extra time writing toString2() by hand will not provide a better user experience for our web app in the slightest.  Am I saying you should never use StringBuilder?  Absolutely not.  In this case, it took me about 1 second to have Eclipse generate the toString() method for me.  And it was implemented correctly.  I wasted time by hand-coding toString2().  I tried to optimize my code, when I had not demonstrated that there was anything wrong with Eclipse’s toString() method.

Now, you might say this is a trivial example.  And it is, but how many times have you spent a bunch of extra time optimizing some code, thinking that the simple way of writing it will be too slow?  It has happened too often to me, and I’m sure it has happened too often to you.

In Principles of Program Design, MA Jackson puts it best:

We follow two rules in the matter of optimization:

Rule 1.  Don’t do it.

Rule 2 (for experts only).  Don’t do it yet – that is, not until you have a perfectly clear and unoptimized solution.

Joshua Bloch, in Effective Java, adds a 3rd rule:

Measure performance before and after each attempted optimization.

You should design your systems well and implement your code so that it works correctly.  Only after you have demonstrated that there is some performance problem should you go back and spend time optimizing it.  Even then, you should be using a profiler to measure how much “more optimal” your optimized version is.

Lots of developers have big egos and think they are clever.  They like to design complicated solutions to simple problems, thinking they will run faster or use less memory.  But the next time you find yourself over-engineering a solution to something, remember the words of the experts: implement your code correctly first, then only optimize it if there is a performance problem.

Advertisements

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s