Oto dwa sposoby ustawienia indywidualnego bitu w C na x86-64:Skuteczność optmization GCC na operacjach bitowych
inline void SetBitC(long *array, int bit) {
//Pure C version
*array |= 1<<bit;
}
inline void SetBitASM(long *array, int bit) {
// Using inline x86 assembly
asm("bts %1,%0" : "+r" (*array) : "g" (bit));
}
Korzystanie GCC 4.3 z -O3 -march=core2
opcji, wersja C trwa około 90% więcej czasu w przypadku używania ze stałą bit
. (Obie wersje kompilacji dokładnie ten sam kod montażu, z tym, że wersja C, wykorzystuje polecenie or [1<<num],%rax
zamiast instrukcji bts [num],%rax
)
Przy zastosowaniu ze zmienną bit
wersja C działa lepiej ale wciąż znacznie mniejsza niż inline montaż.
Resetowanie, przełączanie i sprawdzanie bitów ma podobne wyniki.
Dlaczego GCC jest tak źle zoptymalizowana w przypadku tak powszechnej operacji? Czy robię coś nie tak z wersją C?
Edytuj: Przepraszam za długie czekanie, oto kod, którego użyłem do testu porównawczego. To faktycznie rozpoczął jako prostego problemu programowania ...
int main() {
// Get the sum of all integers from 1 to 2^28 with bit 11 always set
unsigned long i,j,c=0;
for (i=1; i<(1<<28); i++) {
j = i;
SetBit(&j, 10);
c += j;
}
printf("Result: %lu\n", c);
return 0;
}
gcc -O3 -march=core2 -pg test.c
./a.out
gprof
with ASM: 101.12 0.08 0.08 main
with C: 101.12 0.16 0.16 main
time ./a.out
daje również podobne wyniki.
Czy to naprawdę "wspólna operacja"? Najczęstszym czasem, jaki widzę w manipulacji bitowej, jest przedwczesna optymalizacja przez ludzi, którzy myślą, że zaoszczędzą pamięć, pakując kilka flag w jeden bajt. –
Hmm ... dobry punkt. Mimo to jest to dość prosta operacja i jest powszechna w sterownikach sprzętowych. W każdym razie, chodzi o zmniejszenie wydajności o 90%. –
W programowaniu sterowników jestem prawie pewien, że idiom "lewa zmiana 1 do określenia naszej maski" nie jest używany. Raczej "lub" z predefiniowaną flagą. –