Does restrict help in C if a pointer is already marked const?
Asked Answered
P

5

27

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.

Polyandrous answered 19/1, 2009 at 12:30 Comment(5)
unfortunately, I currently don't have a compiler handy, but if you wish, you can produce the assembly for it (gcc -S for example), and see if it really fetches a[0] every time or not.Hayse
@falstro: I put it on Godbolt. See my answer for a writeup on how gcc and clang handle it, but TL:DR version: use float *__restrict__ result to let them both optimize well. clang also manages to hoist the load with const float *__restrict a, but gcc doesn't.Guillema
You have the Flag C and C++ but const in C and const in C++ are have very different meaning.Caruncle
@Caruncle | Could you elaborate?Blasphemous
@Blasphemous In C, const means read only, the hardware and other processes may can still change it (Example is the result register of a ADC, which would be const and volatile). In C++, const means: It does never change, not be your process, not be other processes, not be hardware.Caruncle
H
18

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.

Hayse answered 19/1, 2009 at 12:35 Comment(2)
Actually, Anteru's pointers are not const, but rather, the data being addressed by the pointers are const.Ticklish
@ToddLehman the data addressed by the pointers might not be const, and in fact might be modified during the function's execution. The const only means that the function cannot modify those variables through those pointers, without a cast.Scavenger
G
12

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 constness 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

Guillema answered 28/4, 2017 at 2:46 Comment(3)
You can even change it through that pointer, by casting away const ; so long as the pointed-to object is not actually a const objectScavenger
@M.M: I'd argue that the result of a cast is a new (unnamed temporary) pointer. That's why I used the wording I did: you can't change it through that pointer. But yes, I agree with your point. I think the main point of const and pointer-to-const is improved compile-time checking. It's not even a promise to the compiler that a function won't cast a pointer-to-const back to a pointer-to-nonconst. e.g. after calling external_function(const int*), gcc and clang reload data loaded from that pointer..Guillema
@M.M: updated the answer with more stuff about the effect of restrict on read-only pointers. I found a case where restrict lets clang (but not gcc) avoid a reload after a non-inline function.Guillema
W
11

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).

Weather answered 25/10, 2013 at 15:10 Comment(4)
That's not an answer.Leontine
'const float* a' defines constant address (pointer value) of the pointer, but still says nothing about the data pointed. It depends how you use the definitions const pointer and storage, but stating that it's not a const pointer, but a const storage is really ambiguous and may even confuse someone more.Stig
@lalamer: I think "pointer to const" is a good term for pointers like const float *p. Of course, people will still say and think "const pointer" because it's shorter (and in common usage). But if you ever need to be precise, this is the most clear terminology. You can even easily talk about a const pointer to const (const float *const fp).Guillema
that is exactly the question I was looking for and the current question is not helping at allEvoke
M
8

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.

Macknair answered 25/8, 2011 at 16:21 Comment(0)
T
1

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.

Tarpan answered 26/5, 2009 at 16:29 Comment(1)
Actually the compiler assumes that nothing external to the function changes memory that isn't declared volatile. (Except another function called from this function, obviously).Selfpossession

© 2022 - 2024 — McMap. All rights reserved.