问题
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
来源:https://stackoverflow.com/questions/26297571/how-to-create-an-uint8-t-array-that-does-not-undermine-strict-aliasing