Как создать массив uint8_t, который не подрывает строгий псевдоним?

Недавно я задал этот вопрос:

Использование этого указателя вызывает странную деоптимизацию в горячем цикле

Проблема заключалась в том, что я писал массив типа uint8_t, и компилятор рассматривал его так, как если бы он мог быть псевдонимом с указателем this метода (типа struct T*), потому что void* и char* (= ​​uint8_t*) всегда может быть любым другим указателем на С++. Такое поведение вызвало упущенную оптимизацию. Конечно, я хочу этого избежать. Поэтому возникает вопрос: могу ли я объявить массив uint8_t, который обеспечивает строгое сглаживание, т.е. Что компилятор рассматривает как никогда не псевдонимы с любым указателем другого типа? Я., я ищу что-то вроде типа strict_uint8_t, который является uint8_t со специальным псевдонимом. Есть ли способ достичь этого?

Пример кода, чтобы показать, что я имею в виду, заимствован из другого вопроса и упрощен. Для получения дополнительной информации прочитайте связанный вопрос и его принятый ответ:

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

Вы можете использовать перечисление с фиксированным размером с базовым типом uint8_t:

enum strict_uint8_t : uint8_t {};

Если вы хотите прозрачно конвертировать в и uint8_t, вы можете перенести его в struct с помощью конструктора преобразования и оператора преобразования:

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

Это, по-видимому, устраняет пессимизацию псевдонимов в gcc и clang: https://godbolt.org/g/9Ta98b

(Примечание: предыдущий подход, используя бит-поле, работал в gcc, но не в clang.)

Ответ 2

В visual studio вы можете использовать __declspec(restict) для функций и __restrict для переменных, чтобы сообщить компилятору, что указатель является псевдонимом. Я считаю, что в других компиляторах, таких как GCC, есть атрибут __restrict__ (но я не уверен). Для получения дополнительной информации см. здесь

Ответ 3

Я считаю, что вы избавитесь от псевдонимов, если вы передадите оба указателя через функцию, где указатели объявлены с помощью restrict. Это нестандартное расширение компилятора, например, в случае 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); 
}

В сети: http://goo.gl/SCjpL6