Jump to content

  • Log In with Google      Sign In   
  • Create Account


#Actualtivolo

Posted 08 March 2013 - 04:05 PM

T

Using prefixed member variables (e.g. m_) is useful for writing optimized code.
 
You may or may not know it, but all member variables alias other pointer variables (e.g. function arguments and other members) because of the implicit this-pointer. This affects code optimization and often leads to less optimal code because of redundant loads. Therefore I like all member variables to clearly stand out by using a prefix, so they aren't used in e.g. tight inner loops, but moved to a local variable first. Aliasing basically happens all over the place.
 
Google for "pointer aliasing", "strict aliasing rule", "type punning" and "restricted pointers" if you're interested.

That's something that the compiler should be able to optimize on its own. Accessing a member of a struct can trivially be proven not to alias with another member of the struct. Also, member variables will be moved to registers if they are used in tight inner loops, so there wouldn't be redundant loads.

 

In the case of aliasing, you couldn't be more wrong.

You should not assume what the compiler can and should do, but rather check the facts about what it is allowed to do.

 

First and foremost, the compiler needs to generate correct code. Only then can it create optimized code.

In the case of aliasing, the compiler must be very conservative in order to generate code that behaves correctly in all situations.

 

It cannot be trivially proven that two members don't alias each other. Consider a class with two int* members - if they both point to the same address, they alias each other. And because they are both of type "pointer to int", they are allowed to do so, per the standard. So-called compatible types are allowed to alias, and that also includes signed/unsigned, and cv-qualified types, e.g. a int* is allowed to alias an unsigned int*. In addition, there is a special case for char*, which is allowed to alias everything. You need that to correctly implement certain functionality in case your compiler adheres to the strict aliasing rule (the PS3 compiler does, trust me).

 

You also stated that "member variables will be moved to registers if they are used in tight inner loops, so there wouldn't be redundant loads", and again, this is wrong in case of aliasing.

 

Consider the following simple example:

 

void XorCypher::Encrypt(char* buffer, size_t size)
{
  for (size_t i=0; i<size; ++i)
  {
    buffer[i] ^= m_key;
  }
}

Assume this is a member function of a class XorCypher that has a member named char m_key. In this case, char* buffer aliases char XorCypher::m_key because of the implicit this-pointer (m_key actually becomes a pointer to char). Therefore, the compiler has to generate code that reloads m_key on each and every loop iteration because it might have changed in a store-operation to buffer[i]!

 

Aliasing happens for member variables, function arguments, pointer types, and also reference types. That's why I said "aliasing basically happens all over the place", and you have to be aware of that.

 

What can you do against it? Use local variables, or restricted pointers (using the C99 restrict keyword, supported by all major C++ compilers).

After all, there is a reason why free functions can be faster than member functions, and why the above can be a performance penalty on Xbox360/PS3 platforms.

 

Recommended reading material if you want to know more details:


http://cellperformance.beyond3d.com/articles/2006/06/understanding-strict-aliasing.html

http://cellperformance.beyond3d.com/articles/2006/05/demystifying-the-restrict-keyword.html

http://assemblyrequired.crashworks.org/2008/07/08/load-hit-stores-and-the-__restrict-keyword/


#1tivolo

Posted 08 March 2013 - 04:00 PM

T

Using prefixed member variables (e.g. m_) is useful for writing optimized code.
 
You may or may not know it, but all member variables alias other pointer variables (e.g. function arguments and other members) because of the implicit this-pointer. This affects code optimization and often leads to less optimal code because of redundant loads. Therefore I like all member variables to clearly stand out by using a prefix, so they aren't used in e.g. tight inner loops, but moved to a local variable first. Aliasing basically happens all over the place.
 
Google for "pointer aliasing", "strict aliasing rule", "type punning" and "restricted pointers" if you're interested.

That's something that the compiler should be able to optimize on its own. Accessing a member of a struct can trivially be proven not to alias with another member of the struct. Also, member variables will be moved to registers if they are used in tight inner loops, so there wouldn't be redundant loads.

 

In the case of aliasing, you couldn't be more wrong.

You should not assume what the compiler can and should do, but rather check the facts about what it is allowed to do.

 

First and foremost, the compiler needs to generate correct code. Only then can it create optimized code.

In the case of aliasing, the compiler must be very conservative in order to generate code that behaves correctly in all situations.

 

It cannot be trivially proven that two members don't alias each other. Consider a class with two int* members - if they both point to the same address, they alias each other. And because they are both of type "pointer to int", they are allowed to do so, per the standard. So-called compatible types are allowed to alias, and that also includes signed/unsigned, and cv-qualified types, e.g. a int* is allowed to alias an unsigned int*. In addition, there is a special case for char*, which is allowed to alias everything. You need that to correctly implement certain functionality in case your compiler adheres to the strict aliasing rule (the PS3 compiler does, trust me).

 

You also stated that "member variables will be moved to registers if they are used in tight inner loops, so there wouldn't be redundant loads", and again, this is wrong in case of aliasing.

 

Consider the following simple example:

 

void XorCypher::Encrypt(char* buffer, size_t size)
{
  for (size_t i=0; i<size; ++i)
  {
    buffer[i] ^= m_key;
  }
}

Assume this is a member function of a class XorCypher that has a member named char m_key. In this case, char* buffer aliases char XorCypher::m_key because of the implicit this-pointer (m_key actually becomes a pointer to char). Therefore, the compiler has to generate code that reloads m_key on each and every loop iteration because it might have changed in a store-operation to buffer[i]!

 

Aliasing happens for member variables, function arguments, pointer types, and also reference types. That's why I said "aliasing basically happens all over the place", and you have to be aware of that.

 

What can you do against it? Use local variables, or restricted pointers (using the C99 restrict keyword, supported by all major C++ compilers).

After all, there is a reason why free functions can be faster than member functions, and why the above can be a performance penalty on Xbox360/PS3 platforms.


PARTNERS