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.
> 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.
Good article! This reminds me I need to try your SamplingProfiler on some of my code, hopefully I will help me remove some bottlenecks.
> 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.
Good work mate! Thank you for this very educational example.
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.
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).