Code Optimization: Go For the Jugular

Code optimization can sometimes be experienced as a lengthy process, with disruptive effects on code readability and maintainability. For effective optimization, it is crucial to focus efforts on areas where minimal work and minimal changes will have to most impact, ie. go for the jugular

The Prey

I will illustrate this using SamplingProfiler in a small example, taken from a small library that deals with short vectors of varying length (but usually less than 10 dimensions), which I simplified, isolated & anonymized for the purpose of this article.

uses TypInfo;

type
   TDoWhat = (dwInc, dwDec);

procedure DoSomething1(var data : array of Integer; what : TDoWhat);
var
   i : Integer;
begin
   for i:=Low(data) to High(data) do
   begin
      case what of
         dwInc : Inc(data[i]);
         dwDec : Dec(data[i]);
      else
         raise Exception.Create('Unsupported: '+GetEnumName(TypeInfo(TDoWhat), Integer(what)));
      end;
   end;
end;

 

Get Meat into Belly

Before starting any kind of optimization, one has to define goals and limits, ie. figure out what “good enough” will be rather consider  “good enough” to be the state of the code one has grown tired of optimizing it!

The sample code above is quite straightforward and simple. It would of course be possible to blow this code to huge proportions for optimization’s sake. If you are after getting every last drop of CPU-cycle juice, and allow yourself to use every trick in the book, a fully optimized version could represent several thousandths of lines of code (I’m not exaggerating). If it’s your core business, it might be okay, but if it’s just a utility library, the increased maintainability issues could never be justified.

But since this article is intended more as an illustration than a discussion on the methodology, I’ll get straight to the buffalo (beef). For further reading on that subject, you can start from Big O Notation, Benchmarking and Software metrics articles in wikipedia, there are also whole books on the subject.

Next: Stalking the Prey

6 thoughts on “Code Optimization: Go For the Jugular

  1. > the compiler won’t “know” about the exception in the called procedure

    That’s something that I really miss in Delphi. You can’t give the compiler a hint that a procedure never returns. C++0x has something for this but Delphi doesn’t. Maybe somebody should file a feature request in QC.

  2. Good article! This reminds me I need to try your SamplingProfiler on some of my code, hopefully I will help me remove some bottlenecks.

  3. > the compiler won’t “know” about the exception in the called procedure

    The approach I’d have taken would have been to have a utility function that returns the Exception to be raised.

    ie: line 105 to read
    raise CreateUnsupportedException(what)

    and defined as
    function CreateUnsupportedException(what : TDoWhat): Exception;
    begin
    Result := Exception.Create(‘Unsupported: ‘+GetEnumName(TypeInfo(TDoWhat), Integer(what)));
    end;

    Wouldn’t that still remove the Sting cleanup code into the function but also allow the compiler to “see” the raise.

  4. i think this is one of the most helpful technical articles, on any subject, that i’ve ever seen. It starts with a real example, and it deals with the real questions that result. And what’s good is that you actually focus on the weird stuff, explain why it is, and how to fix it – or why it should not be fixed.

    By dealing with the tough questions, in the same way that they would occur to another developer, you make the task of optimizing seem obvious and natural.

    Actually saying that a CreateFmt would be the first fix, but then explaining why it won’t help in this case, is perfect.

  5. Yes, great educational article, thank you Eric.

    But interestingly I tested this same procedure under Delphi 7 as an exercise to become familiar with SamplingProfiler, and in my tests the exception frame overheads were insignificant.

    The time spent on the “end” statement in DoSomething1 varies between 0 and 11%.

    I tested with array sizes from 40,000,000 to 200,000,000 integers, with the following typical number of samples for each array size:

    Array size: 40M 80M 200M
    DoSomething1 105 212 544
    DoSomething4 104 211 539

    So it seems to me that with Delphi 7 it’s not worth the disadvantages of moving the exception to a procedure.

    Or am I missing something? (The “stack setup” line did not appear in any of my profiling, nor did the “case of” line for DoSomething4).

Comments are closed.