Previous: Repeat Until Belly.Full
Rest Under A Tree
Some quick final notes to conclude.
When moving an exception to a procedure, there are two things to keep in mind:
- the exception will be triggered at another place in the code, to know where it was actually triggered, you’ll have to look up one step in your exception log stack trace… You do have an exception log stack trace in place, don’t you?
- the compiler won’t “know” about the exception in the called procedure, so it will assume execution continues after your RaiseUnsupported, so you may want to place an Exit after it (which will never be reached), to avoid warnings and allow the occasional register optimization by the compiler.
In the final version, we gained more than the previous profiling run hinted at: the new code allowed the compiler to make better use of the registers. Ofttimes, getting the fat out of the way is all you need to see improvements.
If you check the CPU view, you’ll see everything is quite efficient now, but even then, using all the remaining tricks in the book could probably net noteworthy gains, just at a significant complexity increase. I didn’t try, but I would guess a 2x or 3x speed up should be about right.
If you were to need to go that route, SamplingProfiler could still help you there: on ASM code, you get profiling data down to the ASM instruction… but that’s food for another article.
> 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).