If I got the C99 restrict
keyword right, qualifying a pointer with it is a promise made that the data it references won\'t be modified behind the compiler\'s back t
Most of what you know is wrong!
const does not guarantee that something won't change behind the compiler's back. All it does is stop you from writing to that spot. Something else might still be able to write to that location though, so the compiler canNOT assume it's constant.
As others have said, the restrict qualifier is about aliasing. In fact, during the first round of C standardization, there was a proposal for a "noalias" keyword. Unfortunately, the proposal was fairly poorly written -- it prompted the one and only time the Dennis Ritchie got involved during that process, when he wrote a letter that said something to the effect that "noalias must go. This is not open to negotiation."
Needless to say, 'noalias' didn't become part of C. When it came time to try again, the proposal was written enough better that restrict was included in the standard -- and even though noalias would probably have been a more meaningful name for it, that name was so tainted that I doubt anybody even considered trying to use it.
In any case, the primary intent of restrict is to tell the compiler that there will not be an alias of this item. One reason for this is to allow things to be stored in registers temporarily. For example, consider something like:
void f(int *a, int *b, int *c) {
for (int i=0; i<*a; i++)
*b += c[i];
}
The compiler really wants to put i in a register, and load *a into a register, so when it comes time to decide whether to execute another iteration of the loop, it just compares the values in those to registers to each other. Unfortunately, it can't do that -- if somebody who used this function was completely insane, and called it with a==b, every time it writes to *b inside the loop, that new value is also the value of *a -- so it has to read *a from memory at every iteration of the loop, just in case whoever called it was completely insane. Using restrict tells the compiler it can generate code assuming that a and b will always be distinct, so writing to *a will never change *b (or vice versa).
Someone more familiar with the standard could probably give a better answer, but I'll give it a shot.
"Data won't be modified behind the compiler's back" sounds more like the opposite of "volatile" to me.
"const" means the data won't be modified in front of the programmer; that is, she can't modify the data through the signifier marked as "const" (I write "signifier" because in int const *pi
, the name pi
isn't const, but *pi
is). The data might be modifiable via another signifier (non-const data can be passed to a function as const data, after all).
That "restrict" qualifies pointers is the key. Pointers are the only way to alias data in C, so they're the only way that you can access some piece of data via two different names. "restrict" is all about limiting data access to one access path.
This might be an example from an extremely narrow domain, but Altera's Nios II platform is a soft-core microcontroller that you can customize within an FPGA. Then, within the C source code for that micro, you can use a C-to-hardware tool to speed up inner loops using custom hardware, rather than in software.
In there, use of the __restrict__
keyword (which is the same as C99's restrict
) allows the C2H tool to properly optimize the hardware acceleration of pointer operation in parallel instead of sequentially. At least in this case, the restrict
is simply not meant for human consumption. See also Sun's page on restrict, where the first line says
Using the
restrict
qualifier appropriately in C programs may allow the compiler to produce significantly faster executables.
If anyone's interested in reading more on C2H, this PDF discusses optimizing C2H results. The section on __restrict__
is on page 20.
The best 'intuition' to have about the restrict keyword is that its a guarantee (by the programmer to the compiler) that, for the lifetime of the pointer, memory accessed via that pointer will ONLY be accessed via that pointer and not via another pointer or reference or global address. So its important that its on a pointer as its a property of both the pointer and the memory, tying the two together until the pointer goes out of scope.
Your understanding is largely correct. The restrict
qualifier simply states that the data accessed by a so-qualified pointer is only accessed by that exact pointer. It applies to reads as wells as writes.
The compiler doesn't care about concurrent threads, it wasn't going to generate code any differently, and you may clobber your own data as you like. But it does need to know what pointer operations may change what global memory.
Restrict
also carries with it an API warning to humans that a given function is implemented with the assumption of unaliased parameters.
No locking by the user is necessary as far as the compiler is concerned. It only wants to make sure it correctly reads data that was supposed to be clobbered, by code the compiler was supposed to generate, in case there is no restrict
qualifier. Adding restrict
frees it from that concern.
Finally, note that the compiler is likely to already be analyzing possible aliasing based on data types, at the higher optimization levels, so restrict
is important mostly for functions with multiple pointers to the same type of data. You can take a lesson from this subject and make sure that any deliberate aliasing you do is done via a union
.
We can see restrict
in action:
void move(int *a, int *b) { void move(int *__restrict a, int *__restrict b) {
a[0] = b[0]; a[0] = b[0];
a[1] = b[0]; a[1] = b[0];
} }
movl (%edx), %eax movl (%edx), %edx
movl %eax, (%ecx) movl %edx, (%eax)
movl (%edx), %eax movl %edx, 4(%eax)
movl %eax, 4(%ecx)
In the right column, with restrict
, the compiler did not need to reread b[0]
from memory . It was able to read b[0]
and keep it in register %edx
, and then just store the register twice to memory. In the left column, it didn't know if the store to a
may have changed b
.
Chris Dodd has the correct description of the keyword. In certain platforms it can be very important for performance reasons, because it lets the compiler know that once it has loaded data through that pointer onto a register, it need not do so again. Without this guarantee, the compiler must reload the data through a pointer every time any other possibly-aliasing pointer is written through, which can cause a serious pipeline stall called a load-hit-store.
const
and restrict
are different concepts, and it is not the case that const
implies restrict
. All const
says is that you will not write through that pointer within the scope of that function. A const
pointer may still be aliased. For example consider:
int foo( const int *a, int * b )
{
*b *= 2;
return *a + *b; // induces LHS: *a must be read back immediately
// after write has cleared the store queue
}
While you cannot directly write to a
in this function, it would perfectly legal for you to call foo like:
int x = 3;
foo( &x, &x ); // returns 12
restrict
is a different guarantee: a promise that a != b
in all calls to foo()
.
I've written about the restrict keyword and its performance implications at length, and so has Mike Acton. Although we talk about a specific in-order PowerPC, the load-hit-store problem exists on the x86 as well, but the x86's out-of-order execution makes that stall harder to isolate in a profile.
And just to emphasize: this is not an arcane or premature optimization, if you care about performance at all. restrict
can lead to really significant speedups if used correctly.