问题
Just wondering: When I add restrict to a pointer, I tell the compiler that the pointer is not an alias for another pointer. Let's assume I have a function like:
// Constructed example
void foo (float* result, const float* a, const float* b, const size_t size)
{
for (size_t i = 0; i < size; ++i)
{
result [i] = a [0] * b [i];
}
}
If the compiler has to assume that result
might overlap with a
, it has to refetch a each time. But, as a
is marked const
, the compiler could also assume that a is fixed, and hence fetching it once is ok.
Question is, in a situation like this, what is the recommend way to work with restrict? I surely don't want the compiler to refetch a
each time, but I couldn't find good information about how restrict
is supposed to work here.
回答1:
Your pointer is const, telling anyone calling your function that you won't touch the data which is pointed at through that variable. Unfortunately, the compiler still won't know if result is an alias of the const pointers. You can always use a non-const pointer as a const-pointer. For example, a lot of functions take a const char (i.e. string) pointer as a parameter, but you can, if you wish, pass it a non-const pointer, the function is merely making you a promise that it wont use that particular pointer to change anything.
Basically, to get closer to your question, you'd need to add restrict to a and b in order to 'promise' the compiler that whoever uses this function won't pass in result as an alias to a or b. Assuming, of course, you're able to make such a promise.
回答2:
Everyone here seems very confused. There's not a single example of a const pointer in any answer so far.
The declaration const float* a
is not a const pointer, it's const storage. The pointer is still mutable. float *const a
is a const pointer to a mutable float.
So the question should be, is there any point in float *const restrict a
(or const float *const restrict a
if you prefer).
回答3:
Yes, you need restrict. Pointer-to-const doesn't mean that nothing can change the data, only that you can't change it through that pointer.
const
is mostly just a mechanism to ask the compiler to help you keep track of which stuff you want functions to be allowed to modify. const
is not a promise to the compiler that a function really won't modify data.
Unlike restrict
, using pointer-to-const
to mutable data is basically a promise to other humans, not to the compiler. Casting away const
all over the place won't lead to wrong behaviour from the optimizer (AFAIK), unless you try to modify something that the compiler put in read-only memory (see below about static const
variables). If the compiler can't see the definition of a function when optimizing, it has to assume that it casts away const
and modifies data through that pointer (i.e. that the function doesn't respect the const
ness of its pointer args).
The compiler does know that static const int foo = 15;
can't change, though, and will reliably inline the value even if you pass its address to unknown functions. (This is why static const int foo = 15;
is not slower than #define foo 15
for an optimizing compiler. Good compilers will optimize it like a constexpr
whenever possible.)
Remember that restrict
is a promise to the compiler that things you access through that pointer don't overlap with anything else. If that's not true, your function won't necessarily do what you expect. e.g. don't call foo_restrict(buf, buf, buf)
to operate in-place.
In my experience (with gcc and clang), restrict
is mainly useful on pointers that you store through. It doesn't hurt to put restrict
on your source pointers, too, but usually you get all the asm improvement possible from putting it on just the destination pointer(s), if all the stores your function does are through restrict
pointers.
If you have any function calls in your loop, restrict
on a source pointer does let clang (but not gcc) avoid a reload. See these test-cases on the Godbolt compiler explorer, specifically this one:
void value_only(int); // a function the compiler can't inline
int arg_pointer_valonly(const int *__restrict__ src)
{
// the compiler needs to load `*src` to pass it as a function arg
value_only(*src);
// and then needs it again here to calculate the return value
return 5 + *src; // clang: no reload because of __restrict__
}
gcc6.3 (targeting the x86-64 SysV ABI) decides to keep src
(the pointer) in a call-preserved register across the function call, and reload *src
after the call. Either gcc's algorithms didn't spot that optimization possibility, or decided it wasn't worth it, or the gcc devs on purpose didn't implement it because they think it's not safe. IDK which. But since clang does it, I'm guessing it's probably legal according to the C11 standard.
clang4.0 optimizes this to only load *src
once, and keep the value in a call-preserved register across the function call. Without restrict
, it doesn't do this, because the called function might (as a side-effect) modify *src
through another pointer.
The caller of this function might have passed the address of a global variable, for example. But any modification of *src
other than through the src
pointer would violate the promise that restrict
made to the compiler. Since we don't pass src
to valonly()
, the compiler can assume it doesn't modify the value.
The GNU dialect of C allows using __attribute__((pure)) or __attribute__((const)) to declare that a function has no side-effects, allowing this optimization without restrict
, but there's no portable equivalent in ISO C11 (AFAIK). Of course, allowing the function to inline (by putting it in a header file or using LTO) also allows this kind of optimization, and is much better for small functions especially if called inside loops.
Compilers are generally pretty aggressive about doing optimizations that the standard allows, even if they're surprising to some programmers and break some existing unsafe code which happened to work. (C is so portable that many things are undefined behaviour in the base standard; most nice implementations do define the behaviour of lots of things that the standard leaves as UB.) C is not a language where it's safe to throw code at the compiler until it does what you want, without checking that you're doing it the right way (without signed-integer overflows, etc.)
If you look at the x86-64 asm output for compiling your function (from the question), you can easily see the difference. I put it on the Godbolt compiler explorer.
In this case, putting restrict
on a
is sufficient to let clang hoist the load of a[0]
, but not gcc.
With float *restrict result
, both clang and gcc will hoist the load.
e.g.
# gcc6.3, for foo with no restrict, or with just const float *restrict a
.L5:
vmovss xmm0, DWORD PTR [rsi]
vmulss xmm0, xmm0, DWORD PTR [rdx+rax*4]
vmovss DWORD PTR [rdi+rax*4], xmm0
add rax, 1
cmp rcx, rax
jne .L5
vs.
# gcc 6.3 with float *__restrict__ result
# clang is similar with const float *__restrict__ a but not on result.
vmovss xmm1, DWORD PTR [rsi] # outside the loop
.L11:
vmulss xmm0, xmm1, DWORD PTR [rdx+rax*4]
vmovss DWORD PTR [rdi+rax*4], xmm0
add rax, 1
cmp rcx, rax
jne .L11
So in summary, put __restrict__
on all pointers that are guaranteed not to overlap with something else.
BTW, restrict
is only a keyword in C. Some C++ compilers support __restrict__
or __restrict
as an extension, so you should #ifdef
it away on unknown compilers.
Since
回答4:
In the C-99 Standard (ISO/IEC 9899:1999 (E)) there are examples of const * restrict
, e.g., in section 7.8.2.3:
The strtoimax and strtoumax functions
Synopsis
#include <inttypes.h>
intmax_t strtoimax(const char * restrict nptr,
char ** restrict endptr, int base);
--- snip ---
Therefore, if one assumes that the standard would not provide such an example if const *
were redundant to * restrict
, then they are, indeed, not redundant.
回答5:
As the previous answer stated, you need to add "restrict". I also wanted to comment on your scenario that "result might overlap with a". That is not the only reason the compiler will detect that "a" could change. It could also be changed by another thread that has a pointer to "a". Thus, even if your function did not change any values, the compiler will still assume that "a" could change.
来源:https://stackoverflow.com/questions/457441/does-restrict-help-in-c-if-a-pointer-is-already-marked-const