Subsections

10. Optimizations

10.1 Non processor specific

The following sections describe the general optimizations done by the compiler, they are not processor specific. Some of these require some compiler switch override while others are done automatically (those which require a switch will be noted as such).

10.1.1 Constant folding

In Free Pascal, if the operand(s) of an operator are constants, they will be evaluated at compile time.

Example

   x:=1+2+3+6+5;
will generate the same code as
   x:=17;

Furthermore, if an array index is a constant, the offset will be evaluated at compile time. This means that accessing MyData[5] is as efficient as accessing a normal variable.

Finally, calling Chr, Hi, Lo, Ord, Pred, or Succ functions with constant parameters generates no run-time library calls, instead, the values are evaluated at compile time.

10.1.2 Constant merging

Using the same constant string two or more times generates only one copy of the string constant.

10.1.3 Short cut evaluation

Evaluation of boolean expression stops as soon as the result is known, which makes code execute faster then if all boolean operands were evaluated.

10.1.4 Constant set inlining

Using the in operator is always more efficient then using the equivalent <>, =, <=, >=, < and > operators. This is because range comparisons can be done more easily with in then with normal comparison operators.

10.1.5 Small sets

Sets which contain less then 33 elements can be directly encoded using a 32-bit value, therefore no run-time library calls to evaluate operands on these sets are required; they are directly encoded by the code generator.

10.1.6 Range checking

Assignments of constants to variables are range checked at compile time, which removes the need of the generation of runtime range checking code.

Remark: This feature was not implemented before version 0.99.5 of Free Pascal.

10.1.7 Shifts instead of multiply or divide

When one of the operands in a multiplication is a power of two, they are encoded using arithmetic shift instructions, which generates more efficient code.

Similarly, if the divisor in a div operation is a power of two, it is encoded using arithmetic shift instructions.

The same is true when accessing array indexes which are powers of two, the address is calculated using arithmetic shifts instead of the multiply instruction.

10.1.8 Automatic alignment

By default all variables larger then a byte are guaranteed to be aligned at least on a word boundary.

Furthermore all pointers allocated using the standard runtime library (New and GetMem among others) are guaranteed to return pointers aligned on a quadword boundary (64-bit alignment).

Alignment of variables on the stack depends on the target processor.

Remark: Two facts about alignment:

  1. Quadword alignment of pointers is not guaranteed on systems which don't use an internal heap, such as for the Win32 target.
  2. Alignment is also done between fields in records, objects and classes, this is not the same as in Turbo Pascal and may cause problems when using disk I/O with these types. To get no alignment between fields use the packed directive or the {$PackRecords n} switch. For further information, take a look at the reference manual under the record heading.

10.1.9 Smart linking

This feature removes all unreferenced code in the final executable file, making the executable file much smaller.

Smart linking is switched on with the -Cx command-line switch, or using the {$SMARTLINK ON} global directive.

Remark: Smart linking was implemented starting with version 0.99.6 of Free Pascal.

10.1.10 Inline routines

The following runtime library routines are coded directly into the final executable : Lo, Hi, High, Sizeof, TypeOf, Length, Pred, Succ, Inc, Dec and Assigned.

Remark: Inline Inc and Dec were not completely implemented until version 0.99.6 of Free Pascal.

10.1.11 Case optimization

When using the -O1 (or higher) switch, case statements will be generated using a jump table if appropriate, to make them execute faster.

10.1.12 Stack frame omission

Under specific conditions, the stack frame (entry and exit code for the routine, see section [*]) will be omitted, and the variable will directly be accessed via the stack pointer.

Conditions for omission of the stack frame :

10.1.13 Register variables

When using the -Or switch, local variables or parameters which are used very often will be moved to registers for faster access.

Remark: Register variable allocation is currently an experimental feature, and should be used with caution.

10.1.14 Intel x86 specific

Here follows a listing of the optimizing techniques used in the compiler:

  1. When optimizing for a specific Processor (-Op1, -Op2, -Op3, the following is done:
  2. When optimizing for speed (-OG, the default) or size (-Og), a choice is made between using shorter instructions (for size) such as enter $4, or longer instructions subl $4,%esp for speed. When smaller size is requested, things aren't aligned on 4-byte boundaries. When speed is requested, things are aligned on 4-byte boundaries as much as possible.
  3. Fast optimizations (-O1): activate the peephole optimizer
  4. Slower optimizations (-O2): also activate the common subexpression elimination (formerly called the "reloading optimizer")
  5. Uncertain optimizations (-Ou): With this switch, the common subexpression elimination algorithm can be forced into making uncertain optimizations.

    Although you can enable uncertain optimizations in most cases, for people who do not understand the following technical explanation, it might be the safest to leave them off.

    If uncertain optimizations are enabled, the CSE algortihm assumes that
    The practical upshot of this is that you cannot use the uncertain optimizations if you both write and read local or global variables directly and through pointers (this includes Var parameters, as those are pointers too).

    The following example will produce bad code when you switch on uncertain optimizations:

    Var temp: Longint;
    
    Procedure Foo(Var Bar: Longint);
    Begin
      If (Bar = temp)
        Then
          Begin
            Inc(Bar);
            If (Bar <> temp) then Writeln('bug!')
          End
    End;
    
    Begin
      Foo(Temp);
    End.
    
    The reason it produces bad code is because you access the global variable Temp both through its name Temp and through a pointer, in this case using the Bar variable parameter, which is nothing but a pointer to Temp in the above code.

    On the other hand, you can use the uncertain optimizations if you access global/local variables or parameters through pointers, and only access them through this pointer10.1.

    For example:

    Type TMyRec = Record
                    a, b: Longint;
                  End;
         PMyRec = ^TMyRec;
    
    
         TMyRecArray = Array [1..100000] of TMyRec;
         PMyRecArray = ^TMyRecArray;
    
    Var MyRecArrayPtr: PMyRecArray;
        MyRecPtr: PMyRec;
        Counter: Longint;
    
    Begin
      New(MyRecArrayPtr);
      For Counter := 1 to 100000 Do
        Begin
           MyRecPtr := @MyRecArrayPtr^[Counter];
           MyRecPtr^.a := Counter;
           MyRecPtr^.b := Counter div 2;
        End;
    End.
    
    Will produce correct code, because the global variable MyRecArrayPtr is not accessed directly, but only through a pointer (MyRecPtr in this case).

    In conclusion, one could say that you can use uncertain optimizations only when you know what you're doing.

10.1.15 Motorola 680x0 specific

Using the -O2 switch does several optimizations in the code produced, the most notable being:

10.2 Optimization switches

This is where the various optimizing switches and their actions are described, grouped per switch.

-On:
with n = 1..3: these switches activate the optimizer. A higher level automatically includes all lower levels.
-OG:
This causes the code generator (and optimizer, IF activated), to favor faster, but code-wise larger, instruction sequences (such as "subl $4,%esp") instead of slower, smaller instructions ("enter $4"). This is the default setting.

-Og:
This one is exactly the reverse of -OG, and as such these switches are mutually exclusive: enabling one will disable the other.

-Or:
This setting (once it's fixed) causes the code generator to check which variables are used most, so it can keep those in a register.

-Opn:
with n = 1..3: Setting the target processor does NOT activate the optimizer. It merely influences the code generator and, if activated, the optimizer:
-Ou:
This enables uncertain optimizations. You cannot use these always, however. The previous section explains when they can be used, and when they cannot be used.

10.3 Tips to get faster code

Here, some general tips for getting better code are presented. They mainly concern coding style.

10.4 Floating point

This is where can be found processor specific information on floating point code generated by the compiler.

10.4.1 Intel x86 specific

All normal floating point types map to their real type, including comp and extended.

10.4.2 Motorola 680x0 specific

Early generations of the Motorola 680x0 processors did not have integrated floating point units, so to circumvent this fact, all floating point operations are emulated (with the $E+ switch, which is the default) using the IEEE Single floating point type. In other words when emulation is on, Real, Single, Double and Extended all map to the single floating point type.

When the $E switch is turned off, normal 68882/68881/68040 floating point opcodes are emitted. The Real type still maps to Single but the other types map to their true floating point types. Only basic FPU opcodes are used, which means that it can work on 68040 processors correctly.

Remark: Double and Extended types in true floating point mode have not been extensively tested as of version 0.99.5.

Remark: The comp data type is currently not supported.



root
2000-12-20