How to create an uint8_t array that does not under

2019-04-25 08:29发布

问题:

I recently asked this question:

Using this pointer causes strange deoptimization in hot loop

The problem was that I was writing to an array of type uint8_t and the compiler treated it as if it could alias with the this pointer of the method (of type struct T*), because void* and char* (=uint8_t*) can always alias any other pointer in C++. This behaviour caused a missed optimization opportunity. I want to avoid this, of course. So the question is: Can I declare an uint8_t array that enforces strict aliasing, i.e., that the compiler treats as never aliased with any pointer of another type? I.e., I am looking for something like a strict_uint8_t type that is an uint8_t with special aliasing behaviour. Is there a way to achieve this?

Example code to show what I mean, borrowed from other question and simplified. For more details, read the linked question and its accepted answer:

struct T{
   uint8_t* target;
   void unpack3bit(char* source, int size) {
        while(size > 0){
           uint64_t t = *reinterpret_cast<uint64_t*>(source);
           /** `this->target` cannot be cached in a register here but has
               to be reloaded 16 times because the compiler
               thinks that `this->target` could alias with `this` itself.
               What I want is a special uint8_t type that does not trigger
               this behaviour. */
           this->target[0] = t & 0x7; 
           this->target[1] = (t >> 3) & 0x7;
           this->target[2] = (t >> 6) & 0x7;
           this->target[3] = (t >> 9) & 0x7;
           this->target[4] = (t >> 12) & 0x7;
           this->target[5] = (t >> 15) & 0x7;
           this->target[6] = (t >> 18) & 0x7;
           this->target[7] = (t >> 21) & 0x7;
           this->target[8] = (t >> 24) & 0x7;
           this->target[9] = (t >> 27) & 0x7;
           this->target[10] = (t >> 30) & 0x7;
           this->target[11] = (t >> 33) & 0x7;
           this->target[12] = (t >> 36) & 0x7;
           this->target[13] = (t >> 39) & 0x7;
           this->target[14] = (t >> 42) & 0x7;
           this->target[15] = (t >> 45) & 0x7;
           source+=6;
           size-=6;
           target+=16;
        }
}
};

回答1:

You can use a fixed-size enumeration with base type uint8_t:

enum strict_uint8_t : uint8_t {};

If you want to be able to convert to and from uint8_t transparently, you can wrap it in a struct with converting constructor and conversion operator:

struct strict_uint8_t {
    enum : uint8_t {} i;
    strict_uint8_t(uint8_t i) : i{i} {}
    operator uint8_t() const { return i; }
};

This appears to eliminate the aliasing pessimization in gcc and clang: https://godbolt.org/g/9Ta98b

(Note: the previous approach, using a bitfield, worked in gcc but not in clang.)



回答2:

In visual studio you can use __declspec(restict) for functions and __restrict for variables to tell the compiler that the pointer is alias free. I believe that in other compilers like GCC there is a __restrict__ attribute (but I'm not sure). For more info see here



回答3:

I believe you'll get rid of the aliasing if you pass both pointers through a function where the pointers are declared with restrict. That's non-standard compiler extension though, e.g. in the case of g++:

#include <cstdint>
#include <climits>

struct T{
   uint8_t* target;
  private:
    void unpack3bit(char*__restrict__ source, int size, uint8_t*__restrict__ dst) {
        while(size > 0){
           uint64_t t = *source;
           dst[0] = t & 0x7; 
           dst[1] = (t >> 3) & 0x7;
           dst[2] = (t >> 6) & 0x7;
           dst[3] = (t >> 9) & 0x7;
           dst[4] = (t >> 12) & 0x7;
           dst[5] = (t >> 15) & 0x7;
           dst[6] = (t >> 18) & 0x7;
           dst[7] = (t >> 21) & 0x7;
           dst[8] = (t >> 24) & 0x7;
           dst[9] = (t >> 27) & 0x7;
           dst[10] = (t >> 30) & 0x7;
           dst[11] = (t >> 33) & 0x7;
           dst[12] = (t >> 36) & 0x7;
           dst[13] = (t >> 39) & 0x7;
           dst[14] = (t >> 42) & 0x7;
           dst[15] = (t >> 45) & 0x7;
           source+=6;
           size-=6;
           target+=16;
        }
    }
public:
   void unpack3bit(char* source, int size) {
       unpack3bit(source,size,this->target);
   }

};

void f(int i, T& t, char* source) {
  t.unpack3bit(source, i); 
}

Online: http://goo.gl/SCjpL6