2014-10-10 8 views
7

ja niedawno zadał to pytanie:Jak utworzyć tablicę uint8_t, która nie podważa ścisłego aliasingu?

Using this pointer causes strange deoptimization in hot loop

Problem polegał na tym, że pisałem do tablicy typu uint8_t i kompilator traktuje go tak, jakby to mogło alias ze wskaźnikiem sposobu this (typu struct T*), ponieważ void* i char* (= uint8_t*) zawsze mogą aliasować dowolny inny wskaźnik w C++. Takie zachowanie spowodowało brak możliwości optymalizacji. Oczywiście chcę tego uniknąć. Pytanie brzmi: czy mogę zadeklarować tablicę uint8_t, która wymusza ścisłe aliasingowanie, tj. Czy kompilator traktuje jak nigdy aliasing za pomocą dowolnego wskaźnika innego typu? Oznacza to, że szukam czegoś podobnego do typu , który jest uint8_t ze specjalnym zachowaniem aliasingu. Czy istnieje sposób, aby to osiągnąć?

Przykładowy kod pokazujący, co mam na myśli, pożyczony od innego pytania i uproszczony. Aby uzyskać więcej informacji, zapoznaj się z umieszczonego pytanie i jej akceptowany odpowiedź:

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; 
     } 
} 
}; 
+0

Czy na pewno chcesz to zrobić 'target + = 16'? Wtedy stracisz pierwotny wskaźnik T :: cel. –

+1

Muszę przyznać, że nie rozumiem, co sprawia, że ​​aliasing z 'this' (zamiast jakiejkolwiek innej zmiennej) jest wyjątkowy. Nie jestem również pewien, czy jest to istotne. Zdecydowanie interesujące pytanie i tak. –

+0

@Joachim Pileborg: Tak, wszystko w porządku. Pomyśl o 'celu' jako o" aktualnej głowicy zapisu ". Początek bufora 'target' jest przechowywany gdzie indziej. Poza tym ten kod służy tylko do pokazania problemu :). – gexicide

Odpowiedz

4

Można użyć wyliczenie stałym rozmiarze z typu podstawowego uint8_t:

enum strict_uint8_t : uint8_t {}; 

Jeśli chcesz być w stanie przekształcić się i z uint8_t przejrzysty, można owinąć je w struct z konwersji konstruktora i konwersji operatora:

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

Thi s wydaje się wyeliminować pessimization aliasingu w gcc i brzękiem: https://godbolt.org/g/9Ta98b

. (Uwaga: poprzednie podejście, stosując bitfield, pracował w GCC, ale nie w brzękiem)

+0

Aby wyjaśnić: (w przeciwnym razie całkowicie zbędna) specyfikacja bitfield jest niezbędna, aby usunąć możliwość aliasingu? –

+0

@KonradRudolph dokładnie. – ecatmur

+0

@ecatmur: Czy to jest przenośne? Wydaje się działać z 'gcc'. Ale spróbuj swojego linku używając 'clang'. Wygląda na to, że nie kupiłoby tego hack'a :). Wciąż ładuje się 16 razy. – gexicide

0

w visual studio można użyć __declspec(restict) dla funkcji i __restrict dla zmiennych informujących kompilator, że wskaźnik jest aliasem wolnym. Wierzę, że w innych kompilatorach takich jak GCC jest atrybut __restrict__ (ale nie jestem pewien). Aby uzyskać więcej informacji, zobacz: here

+0

Próbowałem '__restrict__' w' gcc'. Kompilował dobrze, ale niczego nie zmieniał. – gexicide

0

Wierzę, że pozbędziesz się aliasingu, jeśli przekażesz oba wskaźniki przez funkcję, w której wskaźniki są zadeklarowane jako restrict. To nietypowe rozszerzenie kompilatora, np. w przypadku 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

+1

Pamiętaj, że nie potrzebujesz dodatkowej metody. Wystarczy cachowanie 'this-> target' w zmiennej lokalnej jest wystarczające. Jednak Twoja sugestia jest po prostu sposobem obejścia tego konkretnego kodu, a nie odpowiedzią na moje pytanie. Oczywiście zawsze mogę obejść problem, buforując wskaźniki w zmiennych lokalnych. Jest to jednak kłopotliwe i podatne na błędy, ponieważ łatwo o nim zapomnieć. Dlatego szukam typu lub czegoś porównywalnego, który może być po prostu użyty do tablic bajtowych bez martwienia się dalej. – gexicide

Powiązane problemy