|
By Douglas Walls, Sun ONE Tools Group, Sun Microsystems, July 2003 (revused March 2006)
|
|
|
Using the restrict qualifier appropriately in C programs
may allow the compiler to
produce significantly faster executables.
Type Qualifiers
The C89 standards committee added two type qualifiers to C, const and
volatile. The C99 committee added a third type qualifier with
restrict. Individually, and in combination, these type qualifiers
determine the assumptions a compiler makes when it accesses an
object through an lvalue. The definition of an lvalue is an object locator,
typically an identifier or the dereferencing of a pointer.
The syntax and semantics of const were adapted from C++; the
concept itself has appeared in other languages. volatile and
restrict are inventions of the committee; both follow the syntactic
model of const.
Type qualifiers were introduced in part to provide greater control over
optimization. Several important optimization techniques are based on the
principle of cacheing: under certain circumstances the compiler can remember
the last value accessed (read or written) from a location, and use this retained
value the next time that location is read. (The memory, or cache, is typically
a hardware register.) If this memory is a machine register, for instance, the
code can be smaller and faster using the register rather than accessing external
memory.
Type qualifiers can be characterized by the restrictions they
impose on access and cacheing.
- const
No writes through this lvalue. In the absence of this qualifier, writes may
occur through this lvalue.
- volatile
No cacheing through this lvalue: each operation in the abstract semantics must
be performed (that is, no cacheing assumptions may be made, since the location
is not guaranteed to contain any previous value). In the absence of this
qualifier, the contents of the designated location may be assumed to be
unchanged except for possible aliasing.
- restrict
Objects referenced through a restrict-qualified pointer have a special
association with that pointer. All references to that object must directly or
indirectly use the value of this pointer. In the absence of this qualifier,
other pointers can alias this object. Cacheing the value in an object
designated through a restrict-qualified pointer is safe at the beginning of
the block in which the pointer is declared, because no pre-existing aliases
may also be used to reference that object. The cached value must be restored
to the object by the end of the block, where pre-existing aliases again become
available. New aliases may be formed within the block, but these must all
depend on the value of the restrict-qualified pointer, so that they can be
identified and adjusted to refer to the cached value. For a
restrict-qualified pointer at file scope, the block is the body of each
function in the file.
The restrict Qualifier and Aliasing
The restrict qualifier addresses the issue that potential aliasing can inhibit
optimizations. Specifically, if a compiler cannot determine that two different
pointers are being used to reference different objects, then it cannot apply
optimizations such as maintaining the values of the objects in registers rather
than in memory, or reordering loads and stores of these values.
The restrict qualifier was designed to express and extend two types of aliasing
information already specified in the language.
First, if a single pointer is directly assigned the return value from an
invocation of malloc, then that pointer is the sole initial means of access to
the allocated object (that is, another pointer can gain access to that object
only by being assigned a value that is based on the value of the first
pointer). Declaring the pointer to be restrict-qualified expresses this
information to the compiler. Furthermore, the qualifier can be used to extend a
compiler’s special treatment of such a pointer to more general situations. For
example, an invocation of malloc might be hidden from the compiler in another
function, or a single invocation of malloc might be used to allocate several
objects, each referenced through its own pointer.
Second, the library specifies two versions of an object copying function,
because on many systems a faster copy is possible if it is known that the source
and target arrays do not overlap. The restrict qualifier can be used to express
the restriction on overlap in a new prototype that is compatible with the
original version:
void *memcpy(void * restrict s1, const void * restrict s2, size_t n);
void *memmove(void * s1, const void * s2, size_t n);
With the restriction visible to the compiler, a straightforward implementation
of memcpy in C now gives a level of performance that previously required
assembly language or other non-standard means. Thus the restrict qualifier
provides a standard means with which to make, in the definition of any function,
an aliasing assertion of a type that could previously be made only for library
functions.
Some Typical Uses of the restrict Qualifier
The complexity of the specification of the restrict qualifier reflects the fact
that C has a rich set of types and a dynamic notion of the type of an object.
For example, an object does not have a fixed type, but acquires a type when
referenced. Similarly, in some of the library functions, the extent of an array
object referenced through a pointer parameter is dynamically determined, either
by another parameter or by the contents of the array. The full specification is
necessary to determine the precise meaning of a qualifier in any context, and so
must be understood by the compiler. Fortunately, C programmers only need to
understand a few simple patterns of usage explained in the following examples.
A compiler can assume that a file-scope restrict-qualified pointer is the sole
initial means of access to an object, much as if it were the declared name of an
array. This is useful for a dynamically allocated array whose size is not known
until run time. Note in the following example how a single block of storage is
effectively subdivided into two disjoint objects.
float * restrict a1, * restrict a2;
void init(int n)
{
float * t = malloc(2 * n * sizeof(float));
a1 = t; // a1 refers to 1st half
a2 = t + n; // a2 refers to 2nd half
}
A compiler can assume that a restrict-qualified pointer that is a function
parameter is, at the beginning of each execution of the function, the sole means
of access to an object. Note that this assumption expires with the end of each
execution. In the following example, parameters a1 and a2 can be assumed to
refer to disjoint array objects because both are restrict-qualified. This
implies that each iteration of the loop is independent of the others, and so the
loop can be aggressively optimized.
void f1(int n, float * restrict a1, const float * restrict a2)
{
int i;
for ( i = 0; i < n; i++ )
a1[i] += a2[i];
}
A compiler can assume that a restrict-qualified pointer declared with block
scope is, during each execution of the block, the sole initial means of access
to an object. An invocation of the macro shown in the following example is
equivalent to an inline version of a call to the function f1 above.
# define f2(N,A1,A2) \
{ int n = (N); \
float * restrict a1 = (A1); \
float * restrict a2 = (A2); \
int i; \
for ( i = 0; i < n; i++ ) \
a1[i] += a2[i]; \
}
The restrict qualifier can be used in the declaration of a structure member. A
compiler can assume, when an identifier is declared that provides a means of
access to an object of that structure type, that the member provides the sole
initial means of access to an object of the type specified in the member
declaration. The duration of the assumption depends on the scope of the
identifier, not on the scope of the declaration of the structure. Thus a
compiler can assume that s1.a1 and s1.a2 below are used to refer to disjoint
objects for the duration of the whole program, but that s2.a1 and s2.a2 are used
to refer to disjoint objects only for the duration of each invocation of the f3
function.
struct t {
int n;
float * restrict a1, * restrict a2;
};
struct t s1;
void f3(struct t s2) { /* ... */ }
Unions and Typedefs
The meaning of the restrict qualifier for a union member or in a type definition
is analogous. Just as an object with a declared name can be aliased by an
unqualified pointer, so can the object associated with a restrict-qualified
pointer.
This allows the restrict qualifier to be introduced more easily into existing
programs, and also allows restrict to be used in new programs that call
functions from libraries that do not use the qualifier. In particular, a
restrict-qualified pointer can be the actual argument for a function parameter
that is unqualified. On the other hand, it is easier for a translator to find
opportunities for optimization if as many as possible of the pointers in a
program are restrict-qualified.
Further Information
Latest C Programming Language Standard
The 1999 ISO C standard is available for purchase and download in PDF
from the ANSI Electronic Standars Store. Enter "9800" in the search
field to find it. Be sure to also download the free Corrigendum of
corrections to the standard, found with the same search.
C User's Guide
Download the latest user's guide for the Sun Studio C Compiler.
About the Author
Douglas Walls
is the C compiler project lead at Sun Microsystems, and heads the U.S. delegation
to the JTC1/SC22/WG14 international standards committee that developed the
ISO 1999 C Standard.
|
|