[Ada Information Clearinghouse]

Ada '83 Quality and Style:

Guidelines for Professional Programmers

Copyright 1989, 1991,1992 Software Productivity Consortium, Inc., Herndon, Virginia.

CHAPTER 9: Performance

9.1 Improving Execution Speed

In this section...
9.1.1 Pragma Inline
9.1.2 Blocks
9.1.3 Arrays
9.1.4 Mod and Rem Operators
9.1.5 Constraint Checking
Summary of Guidelines from this section


9.1.1 Pragma Inline

guideline

example

procedure Assign (Variable : in out Integer; 
                  Value    : in     Integer); 
pragma Inline (Assign);

... 
procedure Assign (Variable : in out Integer; 
                  Value    : in     Integer) is 
begin 
   Variable := Value; 
end Assign;

rationale

Procedure and function invocations include overhead that is unnecessary when the code involved is very small. These small routines are usually written to maintain the implementation hiding characteristics of a package. They may also simply pass their parameters unchanged to another routine. When one of these routines appears in some code that needs to run faster, either the implementation hiding principle needs to be violated or a pragma Inline can be introduced.

The use of pragma Inline does have its disadvantages. It can create compilation dependencies on the body; i.e., when the specification uses a pragma Inline, both the specification and corresponding body may need to be compiled before the specification can be used. As updates are made to the code, a routine may become more complex (larger) and the continued use of a pragma Inline may no longer be justified.

exception

Although it is rare, Inlining code may increase code size which can lead to slower performance caused by additional paging. A pragma Inline may actually thwart a compiler's attempt to use some other optimization technique such as register optimization.

When a compiler is already doing a good job of selecting routines to be inlined, the pragma may accomplish little, if any, improvement in execution speed.

Language Ref Manual references: 2.8 Pragmas, 6.3 Subprogram Bodies, 6.3.2 Inline Expansion of Subprograms, B Predefined Language Pragmas


9.1.2 Blocks

guideline

example

   ... 
   Initial : Matrix;
   
begin  -- Find_Solution

   Initialize_Solution_Matrix: 
      for Row in Initial'Range(1) loop 
         for Col in Initial'Range(2) loop 
            Initial(Row, Col) := Get_Value(Row, Col); 
         end loop; 
      end loop Initialize_Solution_Matrix;
      
   Converge_To_The_Solution: 
      declare
      
         Solution       : Matrix           := Identity; 
         Min_Iterations : constant Natural := ...;
         
      begin  -- Converge_To_The_Solution 
         for Iterations in 1 .. Min_Iterations loop 
            Converge(Solution, Initial); 
         end loop;
         
      end Converge_To_The_Solution;
      
   ... 
end Find_Solution;

rationale

Late initialization allows a compiler more choices in register usage optimization. Depending on the circumstance, this may introduce a significant performance improvement.

Some compilers incur a performance penalty when declarative blocks are introduced. Careful analysis and timing tests by the programmer may identify those declarative blocks that should be removed.

note

It is difficult to accurately predict through code inspections which declarative blocks improve performance and which degrade performance. However, with these general guidelines and a familiarity with the particular implementation, performance can be improved.

Language Ref Manual references: 5.6 Block Statements


9.1.3 Arrays

guideline

example

-- M, N are variables which change value at runtime. 
type Unconstrained       is array (Integer range M .. N)     of Element; 
type Zero_Based          is array (Integer range 0 .. N - M) of Element; 
type Constrained_0_Based is array (Integer range 0 .. 9)     of Element;

rationale

Unconstrained arrays often leave address and offset calculations until runtime. Constrained arrays can be optimized by performing some calculations once at compile time. A detailed discussion of the tradeoffs and alternatives can be found in NASA (1992).

Although zero based indexing is not as intuitive for humans, it simplifies many of the necessary calculations for indexing into arrays.

note

Generic utilities for handling arrays can be instantiated on constrained or unconstrained arrays with arbitrary indexes. Then the compiler can optimize the utility when the more efficient structure is used (assuming the generic is not sharing code!). Again, further details can be found in NASA (1992).

Language Ref Manual references: 3.6 Array Types, 3.6.1 Index Constraints and Discrete Ranges


9.1.4 Mod and Rem Operators

guideline

example

The following is slow:
for I in 0 .. N loop 
   Update(Arr(I mod Modulator)); 
end loop;

The following is equivalent, and avoids the mod operator:
J := 0; 
for I in 0 .. N loop 
   Update(Arr(J));
   
   if J = Modulator then 
      J := 0; 
   else  -- j < Modulator 
      J := J + 1; 
   end if; 
end loop;

rationale

The mod and rem operators are very convenient, but relatively slow. In isolated cases where performance is of concern, a straightforward mapping to incremental schemes is possible.

note

Most of the incremental schemes that avoid the mod and rem operations are prime candidates for generic utilities. Programmers may then conveniently apply this optimization when needed.


9.1.5 Constraint Checking

guideline

example

In this example, two potential constraint checks are eliminated: If the function, Get_Response, returns String, then the initialization of the variable, Input, would require constraint checking. If the variable, Last, is type Positive, then the assignment inside the loop would require constraint checking.
   ... 
   subtype Name_Index is Positive range 1 .. 32; 
   subtype Name       is String (Name_Index); 
   ... 
   function Get_Response return Name is separate; 
   ... 
begin 
      ... 
   Find_Last_Period: 
   declare 
      -- No Constraint Checking needed for initialization 
      Input       : constant Name       := Get_Response; 
      Last_Period :          Name_Index := 1;
      
   begin  -- Find_Last_Period 
      for I in Input'Range loop 
         if Input(I) = '.' then 
            -- No Constraint Checking needed in  this `tight' loop 
            Last_Period := I; 
         end if;
         
      end loop; 
      ...

rationale

Since runtime constraint checking is associated with slow performance, it is not intuitive that the addition of constrained types could actually improve performance. However, the need for constraint checking appears in many places regardless of the use of constrained subtypes. Even assignments to variables that use the predefined types may need constraint checks. By consistently using constrained types, many of the unnecessary runtime checking can be eliminated. Instead, the checking is usually moved to less frequently executed code involved in system input. In the example, the function, Get_Response, may need to check the length of a user supplied string and raise an exception.

Some compilers can do additional optimizations based on the information provided by constrained types. For example, although an unconstrained array does not have a fixed size, it has a maximum size which can be determined from the range of its index. Performance can be improved by limiting this maximum size to a "reasonable" number. Refer to the discussion on unconstrained arrays found in NASA (1992).

honey Language Ref Manual references: 3.3 Types and Subtypes, 3.3.2 Subtype Declarations, 3.4 Derived Types, 4.5.5 Multiplying Operators


Back to document index