Difference between revisions of "Avoiding implicit try finally section"

From Free Pascal wiki
m (ordinal numbers in syntaxhighlight)
Line 67: Line 67:
 
* Shows a trick how to avoid implicit <syntaxhighlight lang="pascal" enclose="none">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" enclose="none">AnsiString</syntaxhighlight>]]/[[Variant|<syntaxhighlight lang="pascal" enclose="none">Variant</syntaxhighlight>]]/[[Data type|something]] every time procedure is called but e.g. only if some parameter has some particular value).
 
* Shows a trick how to avoid implicit <syntaxhighlight lang="pascal" enclose="none">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" enclose="none">AnsiString</syntaxhighlight>]]/[[Variant|<syntaxhighlight lang="pascal" enclose="none">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 start="0">
+
<syntaxhighlight lang="pascal" line>
 
program implicitExceptionDemo(input, output, stderr);
 
program implicitExceptionDemo(input, output, stderr);
  

Revision as of 16:42, 11 April 2018

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 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. 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 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 	// baseUnix, unix needed only to implement clock
10 	BaseUnix, Unix,
11 	sysUtils;
12 
13 function clock(): int64;
14 var
15 	dummy: tms;
16 begin
17 	clock := fpTimes(dummy);
18 end;
19 
20 // Note: when fooNormal and fooFaster are called
21 //       i is always >= 0, so no exception is ever actually raised.
22 // So string constants SNormal and SResString are not really used.
23 
24 procedure fooNormal(i: integer);
25 var
26 	s: string;
27 begin
28 	if i = -1 then
29 	begin
30 		s := 'Some operation with AnsiString';
31 		raise exception.create(s);
32 	end;
33 end;
34 
35 procedure fooFaster(i: integer);
36 	procedure raiseError;
37 	var
38 		s: string;
39 	begin
40 		s := 'Some operation with AnsiString';
41 		raise exception.create(s);
42 	end;
43 begin
44 	if i = -1 then
45 	begin
46 		raiseError;
47 	end;
48 end;
49 
50 
51 // M A I N =================================================
52 const
53 	testCount = 10000000;
54 var
55 	i: integer;
56 	start: int64;
57 begin
58 	start := clock();
59 	for i := 0 to testCount do
60 	begin
61 		fooNormal(i);
62 	end;
63 	writeLn('time of fooNormal: ', clock() - start);
64 	
65 	start := clock();
66 	for i := 0 to testCount do
67 	begin
68 		fooFaster(i);
69 	end;
70 	writeLn('time of fooFaster: ', clock() - start);
71 end.

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