Optimization : Efficient Programming with the SAS/C Compiler
This
section suggests several ways to write more efficient C code. By taking advantage
of compiler features and the way the compiler generates code for certain C
constructs, you can write programs that execute faster and more efficiently.
A leaf function is a function that calls no other functions.
This property means that the function is always at the end of a calling sequence.
The compiler can tell that a leaf function calls no other functions and takes
advantage of this information. Instead of needing a fresh allocation of stack
space, a leaf function uses a fixed area of storage in the CRAB for its automatic
variables (as long as they do not exceed 128 bytes). This leads to a much
more efficient entry and return sequence for these functions. You should make
heavily used functions leaf functions where possible. Leaf functions are also
known as DSA-less functions because no DSA is required.
The
compiler chooses from several possible algorithms when generating code for
a switch statement. In some instances, the compiler can generate code for
switch statements by using indexed tables with 1- or 2-byte entries. This
ensures quick execution and also minimizes the amount of dataspace used.
For every switch encountered in the source file, the
code generator analyzes the size and execution time that would result from
each algorithm and chooses the best one. For switches with a small number
of cases, one of the nonindexed methods is generally used since the overhead
of the table lookup is not justified. However, for a large number of cases,
an indexed algorithm is used for all but highly sparse switch statements.
Indexed algorithms require one table entry for each
value in the switch range (the difference between the lowest and highest values
in case statements). Therefore, it is advantageous to reduce the range of
switch statements if possible because this reduces table space. If you have
one or two case values that are very different from the others, you may want
to test for them separately or handle them at the default label (which is
not part of the range).
Most
programs that use far pointers access a small number of address spaces. A
typical design may use only the primary address space and one or two dataspaces.
Because the optimizer and compiler cannot in general tell whether two far
pointers reference the same address space, there may be a lot of unnecessary
reloading of the access registers.
A technique that may lead to better generated code is
to use only a few far pointers (one per secondary address space or dataspace)
and then to use offsets rather than pointers to address objects in the dataspaces.
For instance, in place of the following code:
_ _far struct cb *lookup_user(char *);
_ _far struct cb *userptr;
userptr = lookup_user("fred");
userptr->inuse = 1;
You could use the following equivalent code instead:
extern _ _far char *user_data_space;
int lookup_user(char *);
int useroff;
useroff = lookup_user("fred");
((_ _far struct cb *)(user_data_space + useroff))->inuse = 1;
This style allows the optimizer to recognize that the
variable user_data_space
is frequently accessed,
and should have a register reserved for it. This may in turn mean that the
ALET for the user dataspace will only need to be loaded from memory once during
the execution of a particular function.
This style of coding can be made more readable by using
the preprocessor, as shown in the following example:
#define contents(type, alet, offset) \
((_ _far type *)((alet) + (offset)))
#define offset(alet, addr) \
(((_ _far char *) addr) - alet)
#define cb_contents(offset) \
contents(struct cb, user_data_space, offset)
extern _ _far char *user_data_space;
int lookup_user(char *);
int useroff;
useroff = lookup_user("fred");
cb_contents(useroff)->inuse = 1;
Copyright © 2001
by SAS Institute Inc., Cary, NC, USA. All rights reserved.