Practical benefits of the restrict keyword

This topic is 1706 days old which is more than the 365 day threshold we allow for new replies. Please post a new topic.

Recommended Posts

Recently I've re-discovered the restrict keyword. Though not part of the C++ standard yet, it is supported by most compilers. For anyone not familiar with it, wikipedia has a good introduction: http://en.wikipedia.org/wiki/Restrict In theory, this keyword allows the compiler to optimize read and write operations and might be particularly useful in the case of loops that process long sequences of data. See http://assemblyrequired.crashworks.org/2008/07/08/load-hit-stores-and-the-__restrict-keyword/.
In my engine I've adopted a functional style at a low level and I could apply this keyword to most, perhaps all, my functions. My question is: is it really worth it ? Before I apply it blindly to my code, I would like you to hear about your experience with it.  Examples with actual performance measurements would be great.

Thanks,

Stefano

Edited by Reitano

Share on other sites

Yes, in performance-critical code it is absolutely worth it.

If your functions take output & input data by pointers to (const) data, many of them will probably be subject to pointer aliasing. Restricting pointers in such a case can remove a lot of redundant load operations which have to be inserted in case of aliasing between compatible types.

I use it a lot in my engine, especially when dealing with large amounts of data, e.g. per-frame component updates for all entities in the world, or frustum culling all AABBs, skinning characters, etc. When using SoA rather than AoS, the writes to the different SoA-streams can lead to a lot of redundant loads, due to possible aliasing. See here for a concrete example.

As for actual performance measurements, I once optimized a (large, often-used) function by about 6ms on the PS3's PPU just by properly restricting pointers, and removing a few load-hit-stores in the process.

Even though you pointed out that you mostly use a functional style, I nevertheless want to point out that class members are almost always subject to aliasing other pointer types, due to the implicit this-pointer. Many people fail to realize this, but e.g. a simple "char" member can alias any char* input argument in all your class methods. Because of that, most C++ compilers also support restricting whole member functions simply by putting the restrict keyword at the end of the method's declaration.

Edited by tivolo

Share on other sites

In my engine I've adopted a functional style at a low level and I could apply this keyword to most, perhaps all, my functions. My question is: is it really worth it ?
No. Completely unnecessary for anything that is not performance critical. How is performance distribution in your program? For me it's about 85/15 and about 10% out of my control. Do not consider optimizations before you have profile data. Adding this keyword to "most, perhaps all" of your functions only makes them harder to read and gives you nothing back.

Share on other sites

I was not aware of the restrict keyword applied to class methods. Likely I will never use it as all my performance-critical low level functions are static methods inside classes, and as static methods they do not take as argument an implicit this pointer.

I have identified some performance hot spots and I will see if there is any performance gain when using this keyword.

@Tivolo: how do you store entity components in your engine ? Currently I use an std::vector of pointers allocated from a pool. Can I apply the restrict keyword in this case ?

Share on other sites

@Tivolo: how do you store entity components in your engine ? Currently I use an std::vector of pointers allocated from a pool. Can I apply the restrict keyword in this case ?

The restrict keyword is not transitive, which means that storing a std::vector<MyComponent* restrict> doesn't make a difference, as long as you don't also access them by using a restricted pointer, e.g. MyComponent* restrict comp = componentVector[index];

In my engine, the data of individual components is stored in SoA-fashion, using plain arrays that point into a contiguous chunk of memory.

Share on other sites

Great explanation Hodgman!

struct Foo { void AddSumOfData(int*); int m_size; int* m_data; };
{
int counter = *out;
for( int i=0, end = m_size; i != end; ++i )
counter += m_data[i];
*out = counter;
}

That's the same code, but now with no aliasing opportunities, so there's no need to go using restrict any more! It will compile much more optimally without further tweaking.
The out pointer is read once at the start and written to once at the end, instead of a read+write every single iteration.
The m_size variable is read once at the start of the loop now, instead of once every single iteration.
The loop can potentially be unrolled or pre-fetched now, because the loop body only modifies local variables, which we know are on the local stack and can't possibly alias anything (so it's basically a read-only loop now).

Is it really necessary to copy m_size to a local? Since the loop only modifies local variables isn't the compiler able to optimize it and keep m_size in a register?

Share on other sites

Is it really necessary to copy m_size to a local? Since the loop only modifies local variables isn't the compiler able to optimize it and keep m_size in a register?
Good spot Yep, one or the other would do -- you could either read m_size to a local variable, or you could make the counter local variable for use in the loop. Just doing one of those things is enough to stop the write to out from aliasing with the read from m_size.

I've gotten into the habit of always writing my for loops like the above though (with the end variable), so that came naturally to me. In any case, copying m_size to a local isn't going to make the compiled code any worse, so there's no harm in anti-aliasing all the variables

1. 1
2. 2
3. 3
4. 4
Rutin
18
5. 5

• 14
• 12
• 9
• 12
• 37
• Forum Statistics

• Total Topics
631423
• Total Posts
3000005
×