2015-11-05 16 views
7

Mam następującą funkcję:
Get bity z bajtu

int GetGroup(unsigned bitResult, int iStartPos, int iNumOfBites) 
{ 
return (bitResult >> (iStartPos + 1- iNumOfBites)) & ~(~0 << iNumOfBites); 
} 

Funkcja zwraca grupę bity z bajtu.
tj jeśli bitResult=102 (01100110)2, iStartPos=5, iNumOfBites=3
wyjściowa: 2 (10)2
Dla iStartPos=7, iNumOfBites=4
wyjściowa: 3 (0110)2

szukam lepszy sposób/"przyjazny", aby to zrobić, tzn z bitset czy coś takiego.
Wszelkie sugestie?

+5

Bitsets nie wydaje się właściwe do tego celu. Zapewniają łatwy dostęp do poszczególnych bitów zestawu, ale nie są ciągłymi zakresami elementów. Twoja metoda wygląda dla mnie dobrze. – Barmar

+7

Już sprawiłeś, że jest "przyjazny dla użytkownika" poprzez zawinięcie go w funkcję. – imallett

+0

Czy dane wyjściowe dla 'GetGroup (2, 5,3) nie powinny być' 4 (0b100) '? W GNU calc otrzymuję '(102 >> (5-3 + 1)) & ~ ((~ 0) <<3)' ->' 4/* 0b100 * /. (Po użyciu 'base2 (2)' do ustawienia wyjścia pomocniczego base to binary.) –

Odpowiedz

3
(src >> start) & ((1 << len)-1) 

jest sposobem wyrażania ekstrakcji len bitów, zaczynając start. (W tym przypadku, start jest LSB zakresu, który chcesz. Twoja funkcja wymaga MSB jako wejście.) To wyrażenie pochodzi z Wikipedia's article on the x86 BMI1 instruction set extensions.

Oba sposoby tworzenia maski wyglądają na ryzykowne w przypadku, gdy len to pełna szerokość tego typu. (Narożny przypadek wydobywania wszystkich bitów). Przesunięcia o pełną szerokość typu mogą albo dać zero, albo niezmienione. . (IIRC, to faktycznie wywołuje niezdefiniowanej zachowanie, ale to, co dzieje się w praktyce na przykład x86 masek przesunięcie odliczanie do zakresu 0-31 (dla przesunięcia 32bit) Z int 32bit.

  • Jeśli 1 < < 32 daje 1, a następnie 1/1 = 0, to wynikiem będzie równa zero.

  • Jeśli ~ 0 < < 32 wytwarza ~ 0, raczej niż 0, maska ​​zero.


Myślę, że z twoich przykładów potrzebujesz bitów [iStartPos : iStartPos - iNumOfBites], gdzie bity są ponumerowane od zera.

Najważniejszą rzeczą, którą zmienię w Twojej funkcji, to nazewnictwo funkcji i zmiennych oraz dodanie komentarza.

  • bitResult jest wejściem do funkcji; nie używaj "wyniku" w nazwie.
  • iStartPos ok, ale trochę gadatliwy
  • iNumOfBites Komputery mają bity i bajty. Jeśli masz do czynienia z ugryzieniem, potrzebujesz lekarza (lub dentysty).

Ponadto typem zwrotu prawdopodobnie powinien być unsigned.

// extract bits [msb : msb-len] from input into the low bits of the result 
unsigned BitExtract(unsigned input, int msb, int len) 
{ 
    return (input >> (msb-len + 1)) & ~(~0 << len); 
} 

Jeżeli parametr start-pozycja była LSB, zamiast MSB wyrażenie byłoby prostsze, a kod będzie mniejszy i szybszy (chyba, że ​​po prostu ma dodatkową pracę dla rozmówcy). Z LSB jako paramem, BitExtract ma 7 instrukcji, a 9, jeśli jest to MSB (na x86-64, gcc 5.2).


Dostępne są również instrukcje maszynowe (wprowadzone przez firmę Intel Haswell i AMD Piledriver), które wykonują tę operację. Otrzymasz nieco mniejszy i nieco szybszy kod, używając go. Używa także LSB, konwencji pozycji len, a nie MSB, więc dostajesz krótszy kod z LSB jako argumentem.

Procesory Intela znają tylko wersję, która wymaga natychmiastowego załadowania bezpośrednio do rejestru, więc gdy wartości są stałymi w czasie kompilacji, nie oszczędzają one dużo w porównaniu do zwykłego przesuwania i maskowania. e.g. see this post about using it or pextr for RGB32 -> RGB16. I oczywiście nie ma znaczenia, czy parametrem jest MSB, czy LSB o pożądanym zakresie, jeśli start i len są zarówno stałymi w czasie kompilacji.

Tylko AMD wprowadza wersję bextr że może mieć maskę sterowania jako natychmiastowego stała, ale niestety wydaje gcc 5.2 nie korzysta z natychmiastowej wersji dla kodu, który używa wewnętrzna (nawet z -march=bdver2 (tj spychacz v2 aka piledriver). (będzie generate bextr with an immediate argument on its own in some cases z -march=bdver2.)

I tested it out on godbolt, aby zobaczyć, jaki rodzaj kodu można dostać z lub bez bextr.

#include <immintrin.h> 
// Intel ICC uses different intrinsics for bextr 

// extract bits [msb : msb-len] from input into the low bits of the result 
unsigned BitExtract(unsigned input, int msb, int len) 
{ 
#ifdef __BMI__ // probably also need to check for __GNUC__ 
    return __builtin_ia32_bextr_u32(input, (len<<8) | (msb-len+1)); 
#else 
    return (input >> (msb-len + 1)) & ~(~0 << len); 
#endif 
} 

byłoby wziąć dodatkowy dyspozycję (a) do movzx zaimplementować a (msb-len+1)&0xff kontrola bezpieczeństwa, aby uniknąć rozłożenia bajtu początkowego na bajt długości. Opuściłem to, ponieważ bzdurne jest żądanie wyjścia poza zakres 0-31, nie mówiąc już o zakresie 0-255. Ponieważ nie ulegnie awarii, po prostu zwróć kilka innych nonsensownych wyników, nie ma sensu.

Zresztą bext oszczędza sporo instrukcji (jeśli BMI2 shlx/shrx nie jest dostępny albo! -march=native na godbolt jest Haswell, a zatem obejmuje BMI2 również.)

+0

Jaki byłby efekt wprowadzenia 'inline'? Czy sprawiłoby to, że byłby jeszcze szybszy i prawdopodobnie pozwoliłby na bardziej zlokalizowane optymalizacje? –

+0

@RichardChambers: jak widać z wyjścia godbolt, jeśli pos lub len są stałymi w czasie kompilacji, staje się znacznie bardziej efektywny. W przeciwnym razie zapisuje tylko jedną lub dwie instrukcje 'mov' i oczywiście wywołanie funkcji narzutowej. Inlinowanie może być bardziej wartościowe na x86 (w porównaniu z x86-64), ponieważ x86 ma wolniejsze ABI z parametrem na stosie. –

1
pragma pack(push, 1) 
struct Bit 
{ 
union 
    { 
    uint8_t _value; 
    struct { 
     uint8_t _bit0:0; 
     uint8_t _bit1:0; 
     uint8_t _bit2:0; 
     uint8_t _bit3:0; 
     uint8_t _bit4:0; 
     uint8_t _bit5:0; 
     uint8_t _bit6:0; 
     uint8_t _bit7:0; 
     }; 
    }; 
}; 
#pragma pack(pop, 1) 
typedef Bit bit; 

struct B 
{ 
    union 
    { 
      uint32_t _value; 
      bit bytes[1]; // 1 for Single Byte 
    }; 
}; 

Z struct i unii można ustawić _value Struct B do wyniku, a następnie dostęp bajt [0] ._ bit0 poprzez bajt [0] ._ Bit7 dla każdego 0 lub 1 i vice versa. Ustaw każdy bit, a wynik będzie w wartości _.

+0

To nie pomaga wyodrębnić sąsiednią grupę bitów, np. aby uzyskać zielony komponent z spakowanej wartości piksela rgb. Patrz https: // gcc.gnu.org/ml/gcc/2014-02/msg00202.html na przykład: –

+0

To spowodowałoby niezdefiniowane zachowanie dla C++: http://stackoverflow.com/questions/11373203/accessing-inactive-union-member-undefined -behavior: – Jens

1

Prawdopodobnie zrobiłbym coś takiego, aby zapewnić dodatkową ochronę w przypadku błędów w argumentach i zmniejszyć ilość przesunięć.

Nie jestem pewien, czy rozumiem znaczenie argumentów, których używasz, więc może to wymagać odrobiny poprawek.

I nie jestem pewien, czy jest to koniecznie bardziej wydajne, ponieważ istnieje szereg decyzji i kontroli zasięgu przeprowadzonych w interesie bezpieczeństwa.

/* 
* Arguments: const unsigned bitResult byte containing the bit field to extract 
*    const int iStartPos zero based offset from the least significant bit 
*    const int iNumOfBites number of bits to the right of the starting position 
* 
* Description: Extract a bitfield beginning at the specified position for the specified 
*    number of bits returning the extracted bit field right justified. 
*/ 
int GetGroup(const unsigned bitResult, const int iStartPos, const int iNumOfBites) 
{ 
    // masks to remove any leading bits that need to disappear. 
    // we change starting position to be one based so the first element is unused. 
    const static unsigned bitMasks[] = {0x01, 0x01, 0x03, 0x07, 0x0f, 0x1f, 0x3f, 0x7f, 0xff}; 

    int iStart = (iStartPos > 7) ? 8 : iStartPos + 1; 
    int iNum = (iNumOfBites > 8) ? 8 : iNumOfBites; 

    unsigned retVal = (bitResult & bitMasks[iStart]); 
    if (iStart > iNum) { 
     retVal >>= (iStart - iNum); 
    } 

    return retVal; 
}