Profiling and Optimization

April 30th, 2004

Recently there has been a very interesting and at times heated discussion
about optimization on the HUMBUG
mailing list. It’s worth reading the href="http://lists.humbug.org.au/archives/general/2004-April/thread.html">
archives (look for the “Which is better?” thread) to get the whole
picture.

There were two main topics being discussed:

  1. Whether you should look for optimizations as you code and implement
    them if they don’t affect readability and
  2. Whether or not memory allocation in Java is slow and thus efforts
    should be taken to avoid it.

Issue 1 - Premature Optimization

Inside The Loop or Outside The Loop?

Most people argued that premature optimization is the root of all evil,
and that in Java particularly it’s a waste of time because you almost
always predict what the compiler and the JITC are going to do
incorrectly. There were a couple of people (one in particular) who
thought that optimizations that didn’t affect readability were a good
idea. The example:

for (int i = 0; i < 1000; i++) {
    SimpleDateFormat format = new SimpleDateFormat("YYYY-MM-DD");
    format.format(new Date());
}

was bandied around as an example of something you should optimize by
moving the creation of the SimpleDateFormat out of the loop.  The
speed increase of doing so is very significant and so you should move it
out of the loop, however there are two possible reasons why you would
move it:

  • Because it’s faster outside the loop.
  • Because the scope of the formatter extends across all iterations of the
    loop and not just a single iteration.

It’s for the second reason that I would move the SimpleDateFormat outside
of the loop and not the first.  This is highlighted nicely by one of
the other examples that floated around:

for (int i = 0; i < 1000; i++) {
   Dimension i = new Dimension();
}

 

In this case the creation of Dimension is a much cheaper operation then
creating a SimpleDateFormat and parsing a string (after the first
instance is created as that first instance causes AWT to be
inintialized), so it really doesn’t matter if it’s inside the loop or out
of it in terms of performance but if it were the same exact same
dimension object that was unchanged throughout the iterations, I’d still
move it outside of the loop because it is global to the loop and
unaffected by the current value of the loop counter.  Similarly with
a class like:

public class Format {
    public String formatDate(Date date) {
        SimpleDateFormat format = new SimpleDateFormat("YYYY-MM-DD");
        return format.format(date);
    }
}

I would move the SimpleDateFormat to at least a class variable and, if
every instance of the class used the same format for the date, a static
class variable.  I’d do that simply because it better reflects the
behaviour and intent of the class, not because it improves speed (if the
formatDate method were only called once, or was never called on the
critical path for performance, the change would not improve speed).

So out of this we come to a simple rule that all programmers should know
and follow pretty much automatically:

Scope variables to reflect their usage.  No more than required, no
less.

Forwards Or Backwards?

The original question posted was about loops and whether or not you
should evaluate the termination value (eg: array.length) before the loop
and store it in a local variable to avoid having to recalculate it.
 Now, obviously in Java array.length happens to be a variable anyway
so it’s unlikely to make any difference regardless.  The question
did lead to a bunch of different ways that people might try to optimize
arrays, including going from end to finish to take advantage of the fact
that CPUs can compare to 0 faster than they can compare to other numbers
(apparently).  It was pointed out many times, by many people that
it’s not worth worrying about it because of the small amount of
difference it would make, however one or two people thought that if you
knew in advance techniques to make things run faster, why not use them?
 A benchmark was put forward to determine what the fastest way to
iterate over an array was.  It showed the code:

static Test array_unopt = new Test() {
    public int[] array = new int[10000];
    public void test() {
        for (int i = 0; i < array.length; i += 1)
            continue;
    }
};
static Test array_harry = new Test() {
    public int[] array = new int[10000];
    public void test() {
        int l = array.length;
        for (int i = 0; i < l; i += 1)
            continue;
        }
};
static Test array_aj = new Test() {
    public int[] array = new int[10000];
    public void test() {
        for (int i = array.length - 1; i >= 0; i -= 1)
            continue;
    }
};

took 1570, 1439 and 1456 milliseconds for each test respectively.  I
thought the entire benchmark was href="http://lists.humbug.org.au/archives/general/2004-April/023785.html">
critically flawed.  Even if you take the benchmark as valid, it
showed no noticeable difference in the different ways to iterator over an
array.  Most developers however would expects to see the “for (int i
= 0; i < array.length; i++)” version and would thus recognise it more
easily.  Out of this I would derive the rule (but others didn’t):

Benchmarks are a waste of time.  Profile the real code instead.

 Memory Allocation In Java

The SimpleDateFormat in the for loop example sparked off a significant
debate about how expensive memory allocation was in Java, primarily
because it was heap based rather than stack based.  Obviously using
SimpleDateFormat as a test class is a really bad idea because of the
amount of string parsing it does in it’s constructor.  The other
benchmark that was put forward was creating a lot of Dimension objects in
a loop.  What really killed that benchmark is the fact that creating
a Dimension object requires AWT to be initialized, so when the benchmark
was run, it appeared that Java was exceptionally slow at creating a whole
bunch of objects that effectively did nothing.  The “equivalent”
benchmark in C (which is actually completely different but never mind
that for now):

#include <stdio.h>
#include <time.h>
#include <malloc.h>

int main()
{
  long t = time(0);
  int i;
  for (i = 0; i < 100000000; i += 1)
    free(malloc(16));
  printf("elapsed = %d\n", time(0) - t);
  return 0;
}

Turned out to run 30% slower than the Java benchmark (creating Dimension
objects).  That certainly surprised me.  So even taking into
account the fact that benchmarks are a horrible waste of time, I think
it’s safe to say that memory allocation in Java is a lot faster than even
I thought it was.

From this and a bunch of other suprises in the thread, I’d derive the
rule:

If you haven’t profiled it, you don’t know how well it performs.

Learning

Throughout the thread, there have been a lot of cases where someone
didn’t fully understand a part of the language or library that they
thought they knew well.  This lack of knowledge really stood out
when it came to talking about performance because the performance of an
application can be greatly affected by seemingly trivial details.
 All the people involved in the thread are really quite intelligent
people and most of them have significant experience in the computing
field, but nearly everyone (myself included) managed to come out with a
statement that was at least partially wrong.

From this I’d derive the rule:

You always need to learn more.

Summary

While I’ve learnt a lot of things out of that thread, as well as becoming
very frustrated at times, the main lessons I’ve either learnt from it or
had reaffirmed by it were:

Scope variables to reflect their usage.  No more than required, no
less.

Benchmarks are a waste of time.  Profile the real code instead.

If you haven’t profiled it, you don’t know how well it performs.

You always need to learn more.

Most importantly, many people pointed out the following, but I think Andrae
Muys said it best:

Keep the code simple.
Get the code correct.
Measure.

 

To The Shows!

April 29th, 2004

So I made the mistake of checking out VISA Preferred Seating, promptly followed by QTIX, promptly followed by spending money and wanting to spend more.

I’ve booked tickets to go see The Carer staring the delightful Charles “Bud” Tingwell. I very nearly managed to go see it in Lismore on my recent road trip but the uncultured locals didn’t even realize they had a theatre let alone be able to give me directions to get to it. Sadly the fact that noone knew there was a theatre would have significantly improved the chances that there’d still be a ticket left, but alas despite searching every publicly available map in the town and driving around for an hour or two, I couldn’t find the theatre either.

The other shows I’d like to see are:

  • We Will Rock You, the musical made up of Queens greatest hits which has taken London by storm.
  • Mel Gibson’s The Producers.
  • and possibly “Oh! What a Night” just because I’m a sucker for the 70’s. I can’t actually find any particularly useful information about the show at the moment though it was an ad for it that sparked my interest in seeing what was on.

That should keep my credit card busy for a while…. Course if Miss Saigon were to come back to town I’d be sure to go see that too, having missed it when it came around the first time (mostly because I was too young and living in the barren lands of far north queensland).

Stupid Systems

April 29th, 2004

Every so often you come across a system that is just ridiculously stupid. Telstra’smeans well, but just gets in the way. The particularly problematic feature is the fact that if checks to see if you’ve already paid the particular bill you’re trying to pay and warns you if you already have. That would be great, except for two major flaws:

1. It warns you even if you haven’t paid the bill already.
2. The “I know what I’m doing and you’re just a stupid computer” button, which suggests it will process your payment anyway, just loops you back round to the warning page that you may have already paid the bill before.

This makes it impossible to pay your bill online.

So I pick up the phone and fight my way through their phone payment system, and finally after narrowly avoiding being bored to death by the slow talking recorded voice, I successfully pay my bill. That slow talking recorded voice then tells me how much faster it would have been if I’d paid online.

Sigh.

The Proprietary Catch Revealed

April 29th, 2004

It seems that the proprietary hooks in .Net are becoming more and more clear. This Netcraft interview with Miguel de Icaza ends with the comment:

Longhorn has kind of a scary technology called Avalon, which when compounded with another technology called XAML, it’s fairly dangerous. And the reason is that they’ve made it so it’s basically an HTML replacement. The advantage is it’s probably as easy as writing HTML, so that means that anybody can produce this content with a text editor.

This is the great proprietary catch that ties .Net solely to the Windows platform. No language is useful without a solid set of libraries to work with and the best libraries for .Net (XAML and Avalon) will be Windows only and managed solely by Microsoft, not a standards organization.

It’s not like people haven’t warned about this before though…. Some people still think they can get a good deal out of the devil.

I’m Sick

April 28th, 2004

Feeling sick is no fun…. and daytime TV is really bad.