5.2. Target Code Performance

The target code performance of LEON Ada is generally very good. The compiler generates code that compares well with other compilers, and which the assembly language programmer would find difficult to beat. See the examples of generated code in Appendix A.

The results of running the three benchmark programs Sieve, Ackermann and Whetstone are given in Table 5-1. These programs were run on the simulator, with a 20 MHz clock, zero wait states on data read and write, and one wait state on instruction fetch.

Table 5-1. Benchmark Results

BenchmarkResult at 20 MHzPercent stalls
Ackermann637 mSec5%
Sieve145 mSec27%
Whetstone6410 KWIPS27%

Table 5-2 gives timings for several task-related features. The clock frequency is 20 MHz.

Table 5-2. Task-Related Metrics

MetricClock CyclesTime in Microseconds at 20 MHz
Interrupt latency (C.3.1 (15))30015
From call of trivial protected procedure to return from entry115058
Call of Clock (D.8 (44))131
Lateness of a delay (D.9 (13))110055
Suspend_Until_True, where state is already True502
Set_True to return from Suspend_Until_True101050
Trivial protected procedure call (D.12 (6))1206

5.2.1. Optimization and Code Quality

LEON Ada uses many traditional optimizations to improve the size and execution speed of the generated code. The following list includes some of the optimizations.

The overall level of optimization is controlled by the -O option. The default is optimization level 2. Also many of the optimizations are tied to a further compile-time option and can be enabled or disabled as necessary.

5.2.2. Constraint Checks

In general, constraint checks are eliminated wherever possible, and constraint check expressions are subject to all the usual optimizations.

Most redundant checks are eliminated. In the example that follows, constraint checks such as those at (1), (2) and (3) are generally eliminated.

I : Integer range -2 .. 2;
J : Integer range 0 .. 10;

type BT is access T;
V : BT;

I := 22 mod 3;    -- (1) no checks needed at run time
I := J;           -- (2) check on top limit only
V := new T (...);
if V.L = ... then -- (3) no null access check
                  -- (4) current variant is correct

In the example shown, the run-time checks performed are as follows:

5.2.3. Space for Unused Variables

No space is allocated for scalar variables that are unused. Space for arrays and records is always allocated.

5.2.4. Space for Unused Subprograms

Subprograms that are declared in a package but unused in a program are always loaded if the package is loaded.

5.2.5. Evaluation of Static Expressions

Static expressions are always evaluated according to the rules of the Ada 95 Reference Manual Section 4.8. Other compile-time-constant expressions may be evaluated at compile time too.

5.2.6. Elimination of Unreachable Code

In most cases code that is unreachable is eliminated.

5.2.7. Common Sub-expressions

In the following code example, the address of the element of the array is computed once.


A(I) := A(I) + 1;

5.2.8. Loop Invariants

In the following matrix code, the address of the element A(I, J) is computed for the first iteration, then for subsequent iterations the address is incremented by the size of the element.

for I in 1 .. N loop
   for J in 1 .. M loop
      A (I, J) ...
   end loop;
end loop;

5.2.9. Bound Checks

In general, redundant array bounds checks are eliminated.

5.2.10. The pragma Inline

The pragma Inline is supported, except where the subroutine mentioned in the pragma is ineligible. Inlining across compilation units may be disabled using a compile-time option.

5.2.11. Procedure Calling Overhead

As an example of the subprogram calling overhead, the code sizes for Ackermann's function are as follows:

5.2.12. The Rendezvous

In a rendezvous, the accept statement body is executed by the owning task, never by the calling task. No tasking optimizations are performed but the special case of a null accept statement is handled separately.

5.2.13. Space Requirements

For a task 120 bytes are allocated for the task control block. In addition, there are 12 bytes for each task entry and the task's stack. The stack size is either the default size of 4096 bytes, or the value given in the task type's length clause.

The space overhead for a protected object is 25 bytes.

The size of a null program is approximately 5500 bytes. The size of a minimal program that uses tasking (tasks, protected objects and delay statements) is approximately 10K bytes. These sizes include code, read-only data and variables, but exclude stack space.