Using restrict with arrays?
Asked Answered
Z

2

18

Is there a way to tell a C99 compiler that the only way I am going to access given array is by using myarray[index] ? Say something like this:

int heavy_calcualtions(float* restrict range1, float* restrict range2)
{
    float __I promise I won't alias this__ tmpvalues[1000] = {0};

    ....
    heavy calculations using range1, range2 and tmpvalues;
    ....
}

By using restrict I promised that I won't alias range1 and range2 but how do I do the same thing for array declared inside my function ?

Zorina answered 26/9, 2013 at 11:11 Comment(5)
What kind of warning does it throw?Goal
"invalid use of restrict" - it's supposed to be used with pointers, not arrays (as far as I understand). I could do float* restrict tmpvalues = malloc(sizeof(float)*1000) but then I am not allocating on the stack which may influence the performance as well. Besides telling a compiler that accessing array indexes is safe (so no defensive reads needed) seems like very natural extension of restrict usage with pointers so intuitively there has to be a way to do that.Zorina
I did: float* restrict tmpvalues = alloca(sizeof(float)*1000); memset(tmpvalues, 0, sizeof(float)*1000); and it's measurable improvement but I would prefer doing it in standard (as in C99 complaint way)Zorina
@PiotrLopusiewicz can you post some actual code where float *restrict tmpvalues outperforms the VLA? (pref. with timing code in place already)Lyford
Access the array with a restrict pointer.Moats
C
13

Although Jeff's answer is right, i.e., you can always make a pointer to the allocated array, the fact is that the compiler knows at compile-time that tmpvalues won't be aliased because the variable is declared as an actual array, not a pointer. The only chances to alias an array is declaring a pointer to it, so if you don't do that, there's no need to declare it as restrict. This is more evident if tmpvalues is the only variable you'll have within the function.

The problem may arise if you pass the pointer to another function, then there you should state whether the received pointer is restricted or not.

The documentation I encountered related to this topic includes the C99:

Let D be a declaration of an ordinary identifier that provides a means of designating an object P as a restrict-qualified pointer to type T.

Note that it only applies to pointers.

This other document from TI provides some performance tuning hints using the restrict keyword. In addition to all the hints, section 3.3 provides examples when it is possible to apply this type qualifier and when not. Look for the x array declaration in the middle of page 16, it states that it does not declare a pointer and thus cannot be restrict-qualified.

Collaborationist answered 25/8, 2016 at 7:30 Comment(4)
If a pointer to the array gets passed to outside code, there's no clean way to indicate that outside code won't keep a copy of the pointer and use that copy for arbitrary purposes the next time outside code is invoked. IMHO it would have been helpful for C to have a qualifier similar to restrict but for use with variables whose address has been taken but will only be used in very limited ways, but no such feature is defined.Dumanian
@Dumanian an issue is - how do you define those 'various ways'? gcc has attributes (such as 'pure') that can be attached to functions, which promise that the function won't do certain classes of things. Beyond that, we now have 'link time optimization' where the compiler toolset can basically go look at what the function is actually doing, and use that info.Copolymer
@greggo: If I were writing the rules, I would say that a register-qualified object may only have its address taken in the evaluation of a function argument, and behavior would only be defined in cases where all uses of the resulting pointer or pointers derived therefrom occurs before the function returns, and during execution of the function either (1) the object is accessed exclusively via resulting pointer or others derived from it, or (2) the object is not modified via any means. If a build process starts by building a list of all external symbols used in each function...Dumanian
...a compiler could safely assume that if neither "foo", nor any function called by "foo", makes use of symbol "bar" associated with a register-qualified object, accesses to "bar" may be treated as unsequenced relative to anything that occurs within the function [the definition of every function with a given signature would be regarded as a call from a generic function of that signature type to that function, and an indirect call to a pointer of a signature type would be a call to a generic function of that type, and thus a call to every function matching that signature].Dumanian
M
4

Why can't you do the following? You are not accessing the data associated with tmpvalues via that variable, so it is valid to use a restrict pointer in the compute-intensive portion of the code.

#include <stdio.h>
#include <stdlib.h>

int heavy_calcs(int n, float* restrict range1, float* restrict range2)
{
    if (n>1000) return 1;
    float tmpvalues[1000] = {0};
    {
        float * restrict ptv = tmpvalues;
        for (int i=0; i<n; i++) {
            ptv[i] = range1[i] + range2[i];
        }
    }
    return 0;
}

int main(int argc, char * argv[])
{
    int n = (argc>1) ? atoi(argv[1]) : 1000;
    float * r1 = (float*)malloc(n*sizeof(float));
    float * r2 = (float*)malloc(n*sizeof(float));
    int rc = heavy_calcs(n,r1,r2);
    free(r1);
    free(r2);
    return rc;
}

I ran this through the Intel 15 compiler and it had no trouble vectorizing the loop. Granted, this loop is trivial to compared to what I assume yours is, so your mileage may vary.

Moats answered 28/4, 2015 at 21:7 Comment(2)
No need to cast the result of malloc in CHawkbill
There's no need for the {} around the ptv assignment either.Moats

© 2022 - 2024 — McMap. All rights reserved.