Difference between revisions of "Avoiding implicit try finally section"
Jwdietrich (talk | contribs) |
m (substitute legacy syntaxhighlight syntax) |
||
Line 2: | Line 2: | ||
== Overview == | == Overview == | ||
− | When optimizing code it helps to know that the [[Compiler|compiler]] will wrap certain code constructs in an implicit [[Try|<syntaxhighlight lang="pascal" | + | 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]]. |
− | [[Finally|<syntaxhighlight lang="pascal" | + | 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). |
− | This is needed whenever you use [[Variable|variables]] such as [[Ansistring|<syntaxhighlight lang="pascal" | ||
For example, a procedure like | For example, a procedure like | ||
Line 30: | Line 29: | ||
</syntaxhighlight> | </syntaxhighlight> | ||
− | The compiler thereby ensures that the reference count of <syntaxhighlight lang="pascal" | + | 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. | However, often this may have significant adverse effects on the generated code's speed. | ||
− | 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" | + | 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 <syntaxhighlight lang="pascal" | + | 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. | 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 [[$implicitExeptions|<syntaxhighlight lang="pascal" | + | * 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" | + | * 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" | + | * 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 [[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" | + | * use non-reference-counted types like [[Shortstring|<syntaxhighlight lang="pascal" inline>shortstring</syntaxhighlight>s]]. |
== Risks and when to apply == | == Risks and when to apply == | ||
Line 49: | Line 48: | ||
{{Warning|These exception frames are generated for a reason. If you leave them out any exception in that code will leave a memory leak}} | {{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 [[sImplicitExceptions|<syntaxhighlight lang="pascal" | + | 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]]. |
− | Meanwhile, {{Doc|package=RTL|unit=sysutils|tetx=<syntaxhighlight lang="pascal" | + | Meanwhile, {{Doc|package=RTL|unit=sysutils|tetx=<syntaxhighlight lang="pascal" inline>sysUtils</syntaxhighlight>}} has probably followed. |
For this, the following approach was followed: | 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" | + | * 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. | * 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. | * 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" | + | * 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. |
If you detect problems with these changes please contact [[User:Marcov|Marco]]. | If you detect problems with these changes please contact [[User:Marcov|Marco]]. | ||
Line 66: | Line 65: | ||
time of fooNormal: 141 | time of fooNormal: 141 | ||
time of fooFaster: 17 | time of fooFaster: 17 | ||
− | * Shows a trick how to avoid implicit <syntaxhighlight lang="pascal" | + | * 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). |
<syntaxhighlight lang="pascal" line> | <syntaxhighlight lang="pascal" line> | ||
Line 156: | Line 155: | ||
</syntaxhighlight> | </syntaxhighlight> | ||
− | By putting <syntaxhighlight lang="pascal" | + | 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:Software security]] | [[Category:Software security]] |
Revision as of 11:58, 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 try
… finally
-block.
This is needed whenever you use variables such as ansiString
s, variant
s 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
try…finally
into separate procedures. (You can use procedures in procedures) - use
const
parameters rather than value parameters. This avoids the need to changerefcount
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
shortstring
s.
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.
Meanwhile, sysutils has probably followed.
For this, the following approach was followed:
- A routine that calls a routine that raises exceptions is unsafe – e.g.
strToInt
, but notstrToIntDef
. - 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 thatAnsiString
/Variant
/something every time procedure is called but e.g. only if some parameter has some particular value).
1program implicitExceptionDemo(input, output, stderr);
2
3// for exceptions
4{$mode objfpc}
5// data type 'string' refers to 'ansistring'
6{$longstrings on}
7
8uses
9 {$IFDEF UNIX}
10 // baseUnix, unix needed only to implement clock
11 BaseUnix, Unix,
12 {$ENDIF}
13 sysUtils;
14
15function clock(): int64;
16
17var
18 {$IFDEF UNIX}
19 dummy: tms;
20 {$ELSE}
21 TS : TTimeStamp;
22 {$ENDIF}
23begin
24 {$IFDEF UNIX}
25 clock := fpTimes(dummy);
26 {$ELSE}
27 TS:=DateTimeToTimeStamp(Now);
28 result := TS.Time;
29 {$ENDIF}
30end;
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
38procedure fooNormal(i: integer);
39var
40 s: string;
41begin
42 if i = -1 then
43 begin
44 s := 'Some operation with AnsiString';
45 raise exception.create(s);
46 end;
47end;
48
49procedure 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;
57begin
58 if i = -1 then
59 begin
60 raiseError;
61 end;
62end;
63
64
65// M A I N =================================================
66const
67 testCount = 10000000;
68var
69 i: integer;
70 start: int64;
71begin
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);
85end.
By putting raiseError
into a nested scope of fooFaster
, exception handling does not become part of the main thread of execution.