Difference between revisions of "Avoiding implicit try finally section"

From Free Pascal wiki
m (minor rephrasing for clarity)
(Risks and when to apply: Sysutils does not use implicitexceptions off)
 
(9 intermediate revisions by 4 users not shown)
Line 2: Line 2:
  
 
== Overview ==
 
== 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).
+
When optimizing code it helps to know that the [[Compiler|compiler]] will wrap certain code constructs in an implicit [[Try|<syntaxhighlight lang="pascal" inline>try </syntaxhighlight>]] … [[Finally|<syntaxhighlight lang="pascal" inline>finally</syntaxhighlight>]]-[[Block|block]].
 +
This is needed whenever you use [[Variable|variables]] such as [[Ansistring|<syntaxhighlight lang="pascal" inline>ansiString</syntaxhighlight>s]], [[Variant|<syntaxhighlight lang="pascal" inline>variant</syntaxhighlight>s]] or [[Dynamic array|dynamic arrays]] which require [[Initialization|initialization]] and [[Finalization|finalization]] (i.e. where the standard [[Procedure|procedures]] <syntaxhighlight lang="pascal" inline>initialize</syntaxhighlight> and <syntaxhighlight lang="pascal" inline>finalize</syntaxhighlight> are needed for correct allocation and release of acquired memory).
  
E.g. a procedure like
+
For example, a procedure like
<syntaxhighlight>
+
<syntaxhighlight lang="pascal">
procedure P;
+
procedure doSomething;
var  
+
var
  S: AnsiString;
+
msg: ansiString;
 
begin
 
begin
  ... do something with S ...
+
// do something with msg
 
end;
 
end;
 
</syntaxhighlight>
 
</syntaxhighlight>
 
+
is actually expanded by the compiler to look like this (difference highlighted):
is actually expanded by the compiler to look like this:
+
<syntaxhighlight lang="pascal" highlight="5-6,8-10">
 
+
procedure doSomething;
<syntaxhighlight>
+
var
procedure P;
+
msg: ansiString;
var  
 
  S: AnsiString;
 
 
begin
 
begin
Initialize(S);
+
initialize(msg);
try
+
try
  ... do something with S ...
+
// do something with msg
finally Finalize(S) end;
+
finally
 +
finalize(msg);
 +
end;
 
end;
 
end;
 
</syntaxhighlight>
 
</syntaxhighlight>
  
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.  
+
The compiler thereby ensures that the reference count of <syntaxhighlight lang="pascal" inline>msg</syntaxhighlight> will be properly decremented when <syntaxhighlight lang="pascal" inline>procedure doSomething</syntaxhighlight> exits with [[Exceptions|exception]][s]?.
 +
However, often this may have significant adverse effects on the generated code's speed.
  
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
+
This is issue was a subject on the <tt>fpc-devel</tt> list in the [http://www.mail-archive.com/fpc-devel@lists.freepascal.org/msg01367.html <syntaxhighlight lang="pascal" inline>TList</syntaxhighlight> slowness classes] thread.
  
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.
+
Note, that temporary <syntaxhighlight lang="pascal" inline>ansiString</syntaxhighlight> variables can be created ''implicitly''.
 +
The only way to be completely certain about what actually is being done is to read the [[Assembler|assembler]] output.
  
==Possible solutions==
+
== 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.
+
* use [[$implicitExeptions|<syntaxhighlight lang="pascal" inline>{$implicitexceptions off}</syntaxhighlight>]]: Ensure this applies to release versions only. Debugging can become cumbersome with that switch especially locating memory leaks and corruption.
 +
* split off rarely used code that causes an implicit <syntaxhighlight lang="pascal" inline>try…finally</syntaxhighlight> into separate procedures. (You can use procedures in procedures)
 +
* use [[Const#const parameter|<syntaxhighlight lang="pascal" inline>const</syntaxhighlight> parameters]] rather than value parameters. This avoids the need to change <syntaxhighlight lang="pascal" inline>refcount</syntaxhighlight> but temporary variables could still be an issue.
 +
* use [[Global variables|global variables]]: You have to be careful with reentrancy issues here though and temporary variables could still be an issue.
 +
* use non-reference-counted types like [[Shortstring|<syntaxhighlight lang="pascal" inline>shortstring</syntaxhighlight>s]].
  
* split off rarely used code that causes an implicit try finally into separate procedures. (You can use procedures in procedures)
+
== Risks and when to apply ==
  
* use const parameters rather than value parameters. This avoids the need to change refcount but temporary variables could still be an issue.
+
{{Warning|These exception frames are generated for a reason. If you leave them out any exception in that code will leave a memory leak}}
  
*use global variables. You have to be careful with reentrancy issues here though and temporary variables could still be an issue.
+
In 2007 [[sImplicitExceptions|<syntaxhighlight lang="pascal" inline>{$implicitExceptions}</syntaxhighlight>]] was added to the {{Doc|package=RTL|unit=strutils|text=<syntaxhighlight lang="pascal" inline>strutils</syntaxhighlight>}} [[Unit|unit]].
 +
For this, the following approach was followed:
 +
* A [[Routine|routine]] that calls a routine that [[Raise|raises]] exceptions is unsafe – e.g. {{Doc|package=RTL|unit=sysutils|identifier=strtoint|text=<syntaxhighlight lang="pascal" inline>strToInt</syntaxhighlight>}}, but not {{Doc|package=RTL|unit=sysutils|identifier=strtointdef|text=<syntaxhighlight lang="pascal" inline>strToIntDef</syntaxhighlight>}}.
 +
* 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. {{Doc|package=RTL|unit=sysutils|identifier=datetimeroutines|text=date formatting}} routines.
 +
* Floating point usage can raise exceptions that are converted into catchable exceptions by [[sysutils|<syntaxhighlight lang="pascal" inline>sysUtils</syntaxhighlight>]]. I'm not sure if this really is sufficient reason, but I skipped floating point using routines initially for this reason.
  
*use non refcounted types like shortstrings.
+
If you detect problems with these changes please contact [[User:Marcov|Marco]].
  
==Risks and when to apply==
+
== Demo program ==
  
{{Warning|These exception frames are generated for a reason. If you leave them out any exception in that code will leave a memory leak}}
+
Below is a small demo [[Program|program]] that
  
In 2007 $implicitexceptions was added to the strutils unit. Meanwhile, sysutils has probably followed (''to do: verify''). For this, the following approach was followed:
+
* When run, clearly shows that avoiding an implicit <code>try … finally</code>-block can make code a lot faster. When I run this program on my system, I get
* A routine that calls a routine that raises exceptions is unsafe - e.g. strtoint, but not strtointdef.
+
time of fooNormal: 141
* A routine that raises exceptions itself is unsafe.
+
time of fooFaster: 17
* Very large routines are not worth the trouble, because of the risk and low gains - e.g. date formatting routines.
+
* Shows a trick how to avoid implicit <syntaxhighlight lang="pascal" inline>try … finally</syntaxhighlight>-block (without changing the meaning or safety of the code) in some cases (when you don't need to actually use that [[Ansistring|<syntaxhighlight lang="pascal" inline>AnsiString</syntaxhighlight>]]/[[Variant|<syntaxhighlight lang="pascal" inline>Variant</syntaxhighlight>]]/[[Data type|something]] every time procedure is called but e.g. only if some parameter has some particular value).
* 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.
+
<syntaxhighlight lang="pascal" line>
 +
program implicitExceptionDemo(input, output, stderr);
  
==Demo program==
+
// for exceptions
Below is a small demo program that
+
{$mode objfpc}
* 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
+
// data type 'string' refers to 'ansistring'
Time of Foo_Normal: 141
+
{$longstrings on}
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).
 
  
<syntaxhighlight>
+
uses
{$mode objfpc}{$H+}
+
  {$IFDEF UNIX}
 +
    // baseUnix, unix needed only to implement clock
 +
    BaseUnix, Unix,
 +
  {$ENDIF}
 +
    sysUtils;
  
uses
+
function clock(): int64;
  {BaseUnix, Unix needed only to implement Clock} BaseUnix, Unix,
 
  SysUtils;
 
  
function Clock: Int64;
+
var
var Dummy: tms;
+
  {$IFDEF UNIX}
 +
    dummy: tms;
 +
  {$ELSE}
 +
    TS : TTimeStamp;
 +
  {$ENDIF}
 
begin
 
begin
Clock := FpTimes(Dummy);
+
  {$IFDEF UNIX}
 +
clock := fpTimes(dummy);
 +
  {$ELSE}
 +
  TS:=DateTimeToTimeStamp(Now);
 +
  result := TS.Time;
 +
  {$ENDIF}
 
end;
 
end;
  
procedure Foo_Normal(i: Integer);
+
 
var S: string;
+
 
 +
// Note: when fooNormal and fooFaster are called
 +
//      i is always >= 0, so no exception is ever actually raised.
 +
// So string constants SNormal and SResString are not really used.
 +
 
 +
procedure fooNormal(i: integer);
 +
var
 +
s: string;
 
begin
 
begin
if i = -1 then
+
if i = -1 then
begin
+
begin
  S := 'Some operation with AnsiString';
+
s := 'Some operation with AnsiString';
  raise Exception.Create(S);
+
raise exception.create(s);
end;
+
end;
 
end;
 
end;
  
procedure Foo_Faster(i: Integer);
+
procedure fooFaster(i: integer);
 
+
procedure raiseError;
  procedure RaiseError;
+
var
  var S: string;
+
s: string;
  begin
+
begin
  S := 'Some operation with AnsiString';
+
s := 'Some operation with AnsiString';
  raise Exception.Create(S);
+
raise exception.create(s);
  end;
+
end;
 
 
 
begin
 
begin
if i = -1 then RaiseError;
+
if i = -1 then
 +
begin
 +
raiseError;
 +
end;
 
end;
 
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. }
 
  
 +
// M A I N =================================================
 
const
 
const
  TestCount = 10000000;
+
testCount = 10000000;
 
var
 
var
  i: Integer;
+
i: integer;
  Start: Int64;
+
start: int64;
 
begin
 
begin
Start := Clock;
+
start := clock();
for i := 0 to TestCount do Foo_Normal(i);
+
for i := 0 to testCount do
Writeln('Time of Foo_Normal: ', Clock - Start);
+
begin
 
+
fooNormal(i);
Start := Clock;
+
end;
for i := 0 to TestCount do Foo_Faster(i);
+
writeLn('time of fooNormal: ', clock() - start);
Writeln('Time of Foo_Faster: ', Clock - Start);
+
 +
start := clock();
 +
for i := 0 to testCount do
 +
begin
 +
fooFaster(i);
 +
end;
 +
writeLn('time of fooFaster: ', clock() - start);
 
end.
 
end.
 
</syntaxhighlight>
 
</syntaxhighlight>
  
[[Category:Tutorials]]
+
By putting <syntaxhighlight lang="pascal" inline>raiseError</syntaxhighlight> into a nested [[Scope|scope]] of <syntaxhighlight lang="pascal" inline>fooFaster</syntaxhighlight>, exception handling does not become part of the main thread of execution.
[[Category:FPC]]
+
 
 +
[[Category:Software security]]

Latest revision as of 16:44, 16 May 2020

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

Overview

When optimizing code it helps to know that the compiler will wrap certain code constructs in an implicit tryfinally-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 and release of acquired memory).

For example, a procedure like

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

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

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[s]?. 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

  • use {$implicitexceptions off}: Ensure this applies to release versions only. Debugging can become cumbersome with that switch especially locating memory leaks and corruption.
  • split off rarely used code that causes an implicit tryfinally 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-reference-counted 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. 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 usage 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 fooNormal: 141
time of fooFaster: 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).
 1 program implicitExceptionDemo(input, output, stderr);
 2 
 3 // for exceptions
 4 {$mode objfpc}
 5 // data type 'string' refers to 'ansistring'
 6 {$longstrings on}
 7 
 8 uses
 9   {$IFDEF UNIX}
10     // baseUnix, unix needed only to implement clock
11     BaseUnix, Unix,
12   {$ENDIF}
13     sysUtils;
14 
15 function clock(): int64;
16 
17 var
18   {$IFDEF UNIX}
19     dummy: tms;
20   {$ELSE}
21     TS : TTimeStamp;
22   {$ENDIF}
23 begin
24   {$IFDEF UNIX}
25 	clock := fpTimes(dummy);
26   {$ELSE}
27    TS:=DateTimeToTimeStamp(Now);
28    result := TS.Time;
29   {$ENDIF}
30 end;
31 
32 
33 
34 // Note: when fooNormal and fooFaster are called
35 //       i is always >= 0, so no exception is ever actually raised.
36 // So string constants SNormal and SResString are not really used.
37 
38 procedure fooNormal(i: integer);
39 var
40 	s: string;
41 begin
42 	if i = -1 then
43 	begin
44 		s := 'Some operation with AnsiString';
45 		raise exception.create(s);
46 	end;
47 end;
48 
49 procedure fooFaster(i: integer);
50 	procedure raiseError;
51 	var
52 		s: string;
53 	begin
54 		s := 'Some operation with AnsiString';
55 		raise exception.create(s);
56 	end;
57 begin
58 	if i = -1 then
59 	begin
60 		raiseError;
61 	end;
62 end;
63 
64 
65 // M A I N =================================================
66 const
67 	testCount = 10000000;
68 var
69 	i: integer;
70 	start: int64;
71 begin
72 	start := clock();
73 	for i := 0 to testCount do
74 	begin
75 		fooNormal(i);
76 	end;
77 	writeLn('time of fooNormal: ', clock() - start);
78 	
79 	start := clock();
80 	for i := 0 to testCount do
81 	begin
82 		fooFaster(i);
83 	end;
84 	writeLn('time of fooFaster: ', clock() - start);
85 end.

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