Wiki copymirror

This is a mirror of the wiki at pascal.chat. Editing, history and sign-in are there.

Avoiding implicit try finally section

Bahasa Indonesia · English · Suomi · Русский

Overview

When optimizing code it helps to know that the compiler will wrap certain code constructs in an implicit try … finally statement. This is needed whenever you use variables such as AnsiString, Variant or dynamic arrays which require initialization and finalization (i.e. where the standard procedures initialize and finalize are needed for correct allocation and release of acquired memory).

For example, a procedure like

pascal
procedure doSomething;
var
  msg: ansiString;
begin
  // do something with msg
end;

is actually expanded by the compiler to look like this (difference highlighted):

pascal
procedure doSomething;
var
  msg: ansiString;
begin
  initialize(msg);
  try
    // do something with msg
  finally
    finalize(msg);
  end;
end;

The compiler thereby ensures that the reference count of msg will be properly decremented when procedure doSomething exits with exception. However, often this may have significant adverse effects on the generated code's speed.

This is issue was a subject on the fpc-devel list in the TList slowness classes thread.

Note, that temporary ansiString variables can be created implicitly. The only way to be completely certain about what actually is being done is to read the assembler output.

Possible solutions

Risks and when to apply

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. For this, the following approach was followed:

If you detect problems with these changes please contact Marco.

Demo program

Below is a small demo program that

text
time of fooNormal: 141
time of fooFaster: 17
pascal
program implicitExceptionDemo;

// for exceptions
{$mode objfpc}
// data type 'string' refers to 'ansistring'
{$longstrings on}

uses
  {$IFDEF UNIX}
  BaseUnix, Unix, // needed only to implement clock()
  {$ENDIF}
  sysUtils;

function clock(): int64;
{$IFDEF UNIX}
var
  dummy: tms;
begin
  clock := fpTimes(dummy);
end;
{$ELSE}
var
  TS: TTimeStamp;
begin
  TS := DateTimeToTimeStamp(Now);
  result := TS.Time;
end;
{$ENDIF}

// When fooNormal() and fooFaster() are called
// i is always >= 0, so no exception is ever actually raised,
// and string constants are not really used.

procedure fooNormal(i: integer);
var
  s: string;
begin
  if i = -1 then
  begin
    s := 'Some operation with AnsiString';
    raise Exception.Create(s);
  end;
end;

procedure fooFaster(i: integer);
  //
  procedure RaiseError;
  var
    s: string;
  begin
    s := 'Some operation with AnsiString';
    raise Exception.Create(s);
  end;
  //
begin
  if i = -1 then
  begin
    RaiseError;
  end;
end;

// M A I N =================================================
const
  testCount = 10000000;
var
  i: integer;
  start: int64;
begin
  // normal version
  start := clock();
  for i := 0 to testCount do
  begin
    fooNormal(i);
  end;
  writeLn('time of fooNormal: ', clock() - start);

  // faster version
  start := clock();
  for i := 0 to testCount do
  begin
    fooFaster(i);
  end;
  writeLn('time of fooFaster: ', clock() - start);
end.

By putting raiseError into a nested scope of fooFaster, exception handling does not become part of the main thread of execution.


From the Free Pascal wiki, page "Avoiding implicit try finally section", as it stood on 2024-01-22 after a change by N7800, out of the wiki's dump of 2024-05-26. Copyright stays with the author who wrote it, according to the old wiki's Licensing page. If you have concerns about this material, message the admin.

Revision 3, last changed by WikiArchive .