Avoiding implicit try finally section

From Free Pascal wiki
Jump to: navigation, search

English (en) Bahasa Indonesia (id) русский (ru)

Overview

When optimizing code it helps to know that the compiler will wrap certain code constructs in an implicit try ... finally block. This is needed whenever you use variables such as ansistrings, variants or dynamic arrays which require initialization and finalization (i.e. where the standard procedures Initialize() and Finalize() are needed for correct allocation/deallocation of the memory used).

E.g. a procedure like

procedure P;
var 
  S: AnsiString;
begin
  ... do something with S ...
end;

is actually expanded by the compiler to look like this:

procedure P;
var 
  S: AnsiString;
begin
 Initialize(S);
 try
  ... do something with S ...
 finally Finalize(S) end;
end;

The compiler thereby ensures that the reference count of S will be properly decremented when procedure P exits with exception(s). However, often this can significantly affect the speed of given code adversely.

Here's a link to archived discussion on the fpc-devel list regarding this issue, with the subject "TList slowness in classes" : http://www.mail-archive.com/fpc-devel@lists.freepascal.org/msg01367.html

Note that temporary ansistring variables can be created implicitly. The only way to be completely sure what's going on is to read the assembler output.

Possible solutions

  • use {$implicitexceptions off} Make sure however that you use this only on release versions of your program. Debugging can become a problem with that switch especially locating memory leaks and corruption.
  • split off rarely used code that causes an implicit try finally into separate procedures. (You can use procedures in procedures)
  • use const parameters rather than value parameters. This avoids the need to change refcount but temporary variables could still be an issue.
  • use global variables. You have to be careful with reentrancy issues here though and temporary variables could still be an issue.
  • use non refcounted types like shortstrings.

Risks and when to apply

Warning-icon.png

Warning: These exception frames are generated for a reason. If you leave them out any exception in that code will leave a memory leak

In 2007 $implicitexceptions was added to the strutils unit. Meanwhile, sysutils has probably followed (to do: verify). For this, the following approach was followed:

  • A routine that calls a routine that raises exceptions is unsafe - e.g. strtoint, but not strtointdef.
  • A routine that raises exceptions itself is unsafe.
  • Very large routines are not worth the trouble, because of the risk and low gains - e.g. date formatting routines.
  • Floating point use can raise exceptions that are converted into catchable exceptions by sysutils. I'm not sure if this really is sufficient reason, but I skipped floating point using routines initially for this reason.

If you detect problems with these changes please contact Marco.

Demo program

Below is a small demo program that

  • When run, clearly shows that avoiding an implicit try ... finally block can make code a lot faster. When I run this program on my system, I get
Time of Foo_Normal: 141
Time of Foo_Faster: 17
  • Shows a trick how to avoid implicit try ... finally block (without changing the meaning or safety of the code) in some cases (when you don't need to actually use that AnsiString/Variant/something every time procedure is called but e.g. only if some parameter has some particular value).
{$mode objfpc}{$H+}
 
uses
  {BaseUnix, Unix needed only to implement Clock} BaseUnix, Unix,
  SysUtils;
 
function Clock: Int64;
var Dummy: tms;
begin
 Clock := FpTimes(Dummy);
end;
 
procedure Foo_Normal(i: Integer);
var S: string;
begin
 if i = -1 then
 begin
  S := 'Some operation with AnsiString';
  raise Exception.Create(S);
 end;
end;
 
procedure Foo_Faster(i: Integer);
 
  procedure RaiseError;
  var S: string;
  begin
   S := 'Some operation with AnsiString';
   raise Exception.Create(S);
  end;
 
begin
 if i = -1 then RaiseError;
end;
 
{ Note that when I call Foo_Normal and Foo_ResourceString
  i is always >= 0 so Exception is never actually raised.
  So string constants SNormal and SResString are not really used. }
 
const
  TestCount = 10000000;
var
  i: Integer;
  Start: Int64;
begin
 Start := Clock;
 for i := 0 to TestCount do Foo_Normal(i);
 Writeln('Time of Foo_Normal: ', Clock - Start);
 
 Start := Clock;
 for i := 0 to TestCount do Foo_Faster(i);
 Writeln('Time of Foo_Faster: ', Clock - Start);
end.