zliczanie bitów
-
- Użytkownik
- Posty: 971
- Rejestracja: 27 wrz 2005, o 22:56
- Płeć: Mężczyzna
- Lokalizacja: Wrocław
- Podziękował: 11 razy
- Pomógł: 75 razy
zliczanie bitów
Algorytm zliczania bitów o wartości 1 w liczbie 32-bitowej:
int nbits(unsigned n)
{
int k = 0;
for( ; n != 0; n >>= 1) k += n & 1;
// n >>= 1; - przesuwa bity liczby n, w prawo o 1
// n & 1 - operacja bitowa 'and': n AND 1
return k;
}
Algorytm ten (funkcja) wymaga, najwyżej 32*(1 + 1 + 2) = 32*4 = 128 instrukcji.
Kto zna lepszy?
Język dowolny: paskal, assembler, c, albo nawet schemat blokowy.
int nbits(unsigned n)
{
int k = 0;
for( ; n != 0; n >>= 1) k += n & 1;
// n >>= 1; - przesuwa bity liczby n, w prawo o 1
// n & 1 - operacja bitowa 'and': n AND 1
return k;
}
Algorytm ten (funkcja) wymaga, najwyżej 32*(1 + 1 + 2) = 32*4 = 128 instrukcji.
Kto zna lepszy?
Język dowolny: paskal, assembler, c, albo nawet schemat blokowy.
-
- Użytkownik
- Posty: 204
- Rejestracja: 6 kwie 2005, o 14:43
- Płeć: Mężczyzna
- Lokalizacja: Warszawa
- Pomógł: 23 razy
zliczanie bitów
Może jest jakaś instrukcja assemblera??
Jeśli wydajność jest kluczowa, a operacja jest często powtarzana, to najprościej najpierw przygotować 256-elementową tablicę, zawierającą ilość bitów w kolejnych liczbach od 0 do 255.
Potem zostaje już tylko podzielić 32-bitową liczbę na 4 bajty i sprawdzić w tablicy te 4 bajty i zsumować.
Tak na oko to jakieś 10 operacji.
Jeśli wydajność jest kluczowa, a operacja jest często powtarzana, to najprościej najpierw przygotować 256-elementową tablicę, zawierającą ilość bitów w kolejnych liczbach od 0 do 255.
Potem zostaje już tylko podzielić 32-bitową liczbę na 4 bajty i sprawdzić w tablicy te 4 bajty i zsumować.
Tak na oko to jakieś 10 operacji.
-
- Użytkownik
- Posty: 971
- Rejestracja: 27 wrz 2005, o 22:56
- Płeć: Mężczyzna
- Lokalizacja: Wrocław
- Podziękował: 11 razy
- Pomógł: 75 razy
zliczanie bitów
Mówisz o czymś takim:
byte t8[256] = {0,1,1,2,1,2,2,3,1, ... };
int nbits(byte b4[])
{
return t8[b4[0]] + t8[b4[1]] + t8[b4[2]] + t8[b4[3]];
}
Nie wiem, czy to będzie szybsze - używanie tablicy pośredniej,
która leży w pamięci (do rejestrów jej nie załaduję) może znacznie spowolnić.
Sprawdzę to.
Może masz jakieś inne pomysły - bez tej tablicy.
byte t8[256] = {0,1,1,2,1,2,2,3,1, ... };
int nbits(byte b4[])
{
return t8[b4[0]] + t8[b4[1]] + t8[b4[2]] + t8[b4[3]];
}
Nie wiem, czy to będzie szybsze - używanie tablicy pośredniej,
która leży w pamięci (do rejestrów jej nie załaduję) może znacznie spowolnić.
Sprawdzę to.
Może masz jakieś inne pomysły - bez tej tablicy.
-
- Użytkownik
- Posty: 204
- Rejestracja: 6 kwie 2005, o 14:43
- Płeć: Mężczyzna
- Lokalizacja: Warszawa
- Pomógł: 23 razy
zliczanie bitów
Dokładnie tak. No ale odwołania do pamięci będą jakieś 2 razy wolniejsze niż do rejestrów (a nie 12 razy). Tak więc można sądzić, że będzie to działać przynajmniej 6 razy szybciej.
Jak na razie nic innego nie przychodzi mi do głowy, ale pomyślę, bo ciekawy problem. Swoją drogą: do czego Ci potrzebne zliczanie bitów?
Jak na razie nic innego nie przychodzi mi do głowy, ale pomyślę, bo ciekawy problem. Swoją drogą: do czego Ci potrzebne zliczanie bitów?
-
- Użytkownik
- Posty: 971
- Rejestracja: 27 wrz 2005, o 22:56
- Płeć: Mężczyzna
- Lokalizacja: Wrocław
- Podziękował: 11 razy
- Pomógł: 75 razy
zliczanie bitów
Kiedyś potrzebowałem takiego zliczania przy obróbce obrazów - kombinowałem z wygładzaniem krawędzi (antialiasing).
Są też inne zastosowania np. w algorytmach do kompresji, sumy kontrolne (bit parzystości).
Są też inne zastosowania np. w algorytmach do kompresji, sumy kontrolne (bit parzystości).
-
- Użytkownik
- Posty: 852
- Rejestracja: 23 paź 2004, o 10:17
- Płeć: Mężczyzna
- Lokalizacja: Lublin
- Pomógł: 28 razy
zliczanie bitów
jesli zalezy Ci na wydajnosci dajnej funkcji i jest ona "niskopoziomowa" to zaimplementuj ja w asm i potem w progsie taka wstawke wkleisz
-
- Użytkownik
- Posty: 971
- Rejestracja: 27 wrz 2005, o 22:56
- Płeć: Mężczyzna
- Lokalizacja: Wrocław
- Podziękował: 11 razy
- Pomógł: 75 razy
zliczanie bitów
Co to jest funkcja 'niskopoziomowa'?
Assemblerem można przyspieszyć - góra 50%,
a tu się walczy o grube sety - przynajmniej 500%
Mam pewien pomysł ze zbiorczym sumowaniem bitów:
dodajemy 16 + 16 bitów, teraz 8 + 8 par, ...
- pięć dodawań, ale jest dość sporo manewrów pomocniczych.
Łącznie może, to być gorsze od wariantu z tablicą.
Assemblerem można przyspieszyć - góra 50%,
a tu się walczy o grube sety - przynajmniej 500%
Mam pewien pomysł ze zbiorczym sumowaniem bitów:
dodajemy 16 + 16 bitów, teraz 8 + 8 par, ...
- pięć dodawań, ale jest dość sporo manewrów pomocniczych.
Łącznie może, to być gorsze od wariantu z tablicą.
-
- Użytkownik
- Posty: 852
- Rejestracja: 23 paź 2004, o 10:17
- Płeć: Mężczyzna
- Lokalizacja: Lublin
- Pomógł: 28 razy
zliczanie bitów
to taka ktora mozna w prosty sposob wyrazic za pomoca assemblera tak jak w tym przypadku poniewaz jest tu duzo zabawy z bitamiFibik pisze:Co to jest funkcja 'niskopoziomowa'?
ten sam algorytm w porownaniu w asm w porownaniu do C imho bedzie szybszy bardziej niz o 50%Fibik pisze:Assemblerem mo\u017cna przyspieszy\u0107 - góra 50%
caly problem opiera sie na tym ze instrukcje C nie sa tlumaczone na asm 1:1 tak wiec Twoja funkcja w C ktorej jedno przejscie mialo zajmowac 4 instrukcje w rzeczywistosci jest przez mikroproca wykonywane jako
Kod: Zaznacz cały
int nbits(unsigned n)
{
int k = 0;
for( ; n != 0; n >>= 1) k += n & 1;
// n >>= 1; - przesuwa bity liczby n, w prawo o 1
// n & 1 - operacja bitowa 'and': n AND 1
return k;
}
08048354 <nbits>:
8048354: 55 push %ebp
8048355: 89 e5 mov %esp,%ebp
8048357: 83 ec 04 sub $0x4,%esp
804835a: c7 45 fc 00 00 00 00 movl $0x0,0xfffffffc(%ebp)
8048361: 83 7d 08 00 cmpl $0x0,0x8(%ebp)
8048365: 75 02 jne 8048369 <nbits+0x15>
8048367: eb 10 jmp 8048379 <nbits+0x25>
8048369: 8b 55 08 mov 0x8(%ebp),%edx
804836c: 83 e2 01 and $0x1,%edx
804836f: 8d 45 fc lea 0xfffffffc(%ebp),%eax
8048372: 01 10 add %edx,(%eax)
8048374: d1 6d 08 shrl 0x8(%ebp)
8048377: eb e8 jmp 8048361 <nbits+0xd>
8048379: 8b 45 fc mov 0xfffffffc(%ebp),%eax
804837c: c9 leave
804837d: c3 ret
co do tego zbiorczego sumowania bitow to nie za bardzo rozumiem co miales na mysli
natomiast imho najbardziej wydajny bedzie pierwszy algorytm zaimplementowany w asm
-
- Użytkownik
- Posty: 971
- Rejestracja: 27 wrz 2005, o 22:56
- Płeć: Mężczyzna
- Lokalizacja: Wrocław
- Podziękował: 11 razy
- Pomógł: 75 razy
zliczanie bitów
Nie oszukuj - ten kod assemblera wygenerowałeś właśnie kopmilatorem C.
Jak widać - niewiele da się tam poprawić (zoptymalizować).
Trzeci wariant wygląda tak:
int nbits(uint s)
{
s = (s & 0x55555555) + ((s >> 1) & 0x55555555); // 16 sum po 2
s = (s & 0x33333333) + ((s >> 2) & 0x33333333); // 8 sum po 4 = LHLHLHLH
s = (s & 0x0F0F0F0F) + ((s >> 4) & 0x0F0F0F0F); // 4 sum po 8 =
s = (s & 0x00FF00FF) + ((s >> 8) & 0x00FF00FF); // 2 po 16
return (s & 0xFF) + (s >> 16);
}
Przetestowałem te trzy warianty i wyniki są takie:
Czasy obliczania w milisekundach (dla kilku milionów wywołań):
1400 : 230 : 340
Po przeliczeniu na szybkość i wyrażeniu w stosunku do najwolniejszej w %:
100 : 610 : 410
Zatem:
- algorytm z tablicą jest 6 razy szybszy od pierwszego,
- wersja z binarnym połowieniem i sumowaniem bitów - 4 razy szybsza.
Jak widać - niewiele da się tam poprawić (zoptymalizować).
Trzeci wariant wygląda tak:
int nbits(uint s)
{
s = (s & 0x55555555) + ((s >> 1) & 0x55555555); // 16 sum po 2
s = (s & 0x33333333) + ((s >> 2) & 0x33333333); // 8 sum po 4 = LHLHLHLH
s = (s & 0x0F0F0F0F) + ((s >> 4) & 0x0F0F0F0F); // 4 sum po 8 =
s = (s & 0x00FF00FF) + ((s >> 8) & 0x00FF00FF); // 2 po 16
return (s & 0xFF) + (s >> 16);
}
Przetestowałem te trzy warianty i wyniki są takie:
Czasy obliczania w milisekundach (dla kilku milionów wywołań):
1400 : 230 : 340
Po przeliczeniu na szybkość i wyrażeniu w stosunku do najwolniejszej w %:
100 : 610 : 410
Zatem:
- algorytm z tablicą jest 6 razy szybszy od pierwszego,
- wersja z binarnym połowieniem i sumowaniem bitów - 4 razy szybsza.
-
- Użytkownik
- Posty: 852
- Rejestracja: 23 paź 2004, o 10:17
- Płeć: Mężczyzna
- Lokalizacja: Lublin
- Pomógł: 28 razy
zliczanie bitów
pisalem wlasnie ze to jest kod asm tej funkcji w C....Fibik pisze:Nie oszukuj - ten kod assemblera wygenerowałeś właśnie kopmilatorem C.
-
- Użytkownik
- Posty: 204
- Rejestracja: 6 kwie 2005, o 14:43
- Płeć: Mężczyzna
- Lokalizacja: Warszawa
- Pomógł: 23 razy
zliczanie bitów
Do arigo: poruszasz złożone zagadnienia, warto zauważyć przynajmniej parę rzeczy:
- kompilatory posiadają opcje optymalizacyjne: najmniejszy rozmiar kodu LUB najszybsze wykonanie,
- często użycie asemblera jest zbędne (standardowe operacje na łańcuchach znaków czy np. kopiowanie obszarów pamięci - memmov lub memcpy),
- najlepiej po prostu efektywnie zakodować efektywny algorytm (ale zazwyczaj lepszy rezultat da zakodowanie w C efektywnego algorytmu, niż dłubanie w asm algorytmu nieefektywnego),
- w funkcji z ciałem "return t8[b4[0]] + t8[b4[1]] + t8[b4[2]] + t8[b4[3]]" kompilator będzie chyba skłonny skorzystać ze stosu (wyniki pośrednie operacji arytmetycznych), więc samodzielne zakodowanie tego może przynieść pewne oszczędności (-20%?).
- kompilatory posiadają opcje optymalizacyjne: najmniejszy rozmiar kodu LUB najszybsze wykonanie,
- często użycie asemblera jest zbędne (standardowe operacje na łańcuchach znaków czy np. kopiowanie obszarów pamięci - memmov lub memcpy),
- najlepiej po prostu efektywnie zakodować efektywny algorytm (ale zazwyczaj lepszy rezultat da zakodowanie w C efektywnego algorytmu, niż dłubanie w asm algorytmu nieefektywnego),
- w funkcji z ciałem "return t8[b4[0]] + t8[b4[1]] + t8[b4[2]] + t8[b4[3]]" kompilator będzie chyba skłonny skorzystać ze stosu (wyniki pośrednie operacji arytmetycznych), więc samodzielne zakodowanie tego może przynieść pewne oszczędności (-20%?).
-
- Użytkownik
- Posty: 852
- Rejestracja: 23 paź 2004, o 10:17
- Płeć: Mężczyzna
- Lokalizacja: Lublin
- Pomógł: 28 razy
zliczanie bitów
do drunkard
zdaje sobie sprawe z tego co napisalem. mi chodzilo jedynie o to ze nie mozna wprost porowywac wydajnosci w jezykach wysokiego poziomu mowiac ze ten ma tyle dodawan i tyle mnozen a ten tyle i tyle wiec ten jest lepszy. sam powiedziales ze kompilator czesto stosuje optymalizacje - moze np pewne mozenia zamieniac na przesuniecia bitowe ktore sa duzo szybsze. tak samo np dodawanie liczb z rejestrow jest duzo szybsze niz dodawanie liczb ktore sa w komorkach pamieci gdyz najpierw trzeba wruzycic na stos zawartosc dwoch rejestrow, nastepnie wprowadzic do nich zawartosc powyzszych komorek wykonac operacje zapisac wynik do komorki pamieci i potem zdjac ze stosu rejestry. chodzi mi jedynie o to ze realne i w 100% dokladne porownania algorytmow mozna przeprowadzac tylko i wylacznie w ASM analizujac liczbe cykli rozkazowych
zdaje sobie sprawe z tego co napisalem. mi chodzilo jedynie o to ze nie mozna wprost porowywac wydajnosci w jezykach wysokiego poziomu mowiac ze ten ma tyle dodawan i tyle mnozen a ten tyle i tyle wiec ten jest lepszy. sam powiedziales ze kompilator czesto stosuje optymalizacje - moze np pewne mozenia zamieniac na przesuniecia bitowe ktore sa duzo szybsze. tak samo np dodawanie liczb z rejestrow jest duzo szybsze niz dodawanie liczb ktore sa w komorkach pamieci gdyz najpierw trzeba wruzycic na stos zawartosc dwoch rejestrow, nastepnie wprowadzic do nich zawartosc powyzszych komorek wykonac operacje zapisac wynik do komorki pamieci i potem zdjac ze stosu rejestry. chodzi mi jedynie o to ze realne i w 100% dokladne porownania algorytmow mozna przeprowadzac tylko i wylacznie w ASM analizujac liczbe cykli rozkazowych
-
- Użytkownik
- Posty: 971
- Rejestracja: 27 wrz 2005, o 22:56
- Płeć: Mężczyzna
- Lokalizacja: Wrocław
- Podziękował: 11 razy
- Pomógł: 75 razy
zliczanie bitów
Zły algorytm, przełożony na assembler, albo nawet na kod maszynowy, jest nadal złym algorytmem.
Weźmy przykładowo sortowanie.
Najprostszą metodą sortowania jest zwykłe wyszukiwanie największego elementu z
n elementów, później z n-1, dalej z n-2, i tak aż do 1.
Łączna liczba operacji: N = n + n-1 + ... + 1 = (n+1)n/2 = n^2 + ...
Zatem jest to metoda rzędu n^2.
Znany jest lepszy algorytm (quick sort)- rzędu n*log(n).
Czas wykonywania jednej operacji to t, zatem możemy obliczyć czasy sortowania:
To = n^2 * t; Tq = nlog(n)*t (jest to log o podstawie 2)
Porównajmy te metody: r = To/Tq = n/log(n)
dla n = 1000, r = 1000/10 = 100 -> qsort 100 jest razy szybsza
dla n = 1000,000, r = 1000,000/20 = 50,000 -> qsort 50tyś razy szybsza!
Widać tu, że zamiast marnować czas na doskonalenie pierwszej metody,
trzeba było ją wyrzucić na śmietnik i poszukać innej (całkowicie nowego algorytmu),
no i dzięki takiemu podejściu znaleziono nieporównywalnie szybszą metodę.
Weźmy przykładowo sortowanie.
Najprostszą metodą sortowania jest zwykłe wyszukiwanie największego elementu z
n elementów, później z n-1, dalej z n-2, i tak aż do 1.
Łączna liczba operacji: N = n + n-1 + ... + 1 = (n+1)n/2 = n^2 + ...
Zatem jest to metoda rzędu n^2.
Znany jest lepszy algorytm (quick sort)- rzędu n*log(n).
Czas wykonywania jednej operacji to t, zatem możemy obliczyć czasy sortowania:
To = n^2 * t; Tq = nlog(n)*t (jest to log o podstawie 2)
Porównajmy te metody: r = To/Tq = n/log(n)
dla n = 1000, r = 1000/10 = 100 -> qsort 100 jest razy szybsza
dla n = 1000,000, r = 1000,000/20 = 50,000 -> qsort 50tyś razy szybsza!
Widać tu, że zamiast marnować czas na doskonalenie pierwszej metody,
trzeba było ją wyrzucić na śmietnik i poszukać innej (całkowicie nowego algorytmu),
no i dzięki takiemu podejściu znaleziono nieporównywalnie szybszą metodę.
- juzef
- Użytkownik
- Posty: 890
- Rejestracja: 29 cze 2005, o 22:42
- Płeć: Mężczyzna
- Lokalizacja: Koszalin
- Pomógł: 66 razy
zliczanie bitów
Nie ufam quicksortowi. Jeśli dostanie ładne dane, szybko je posortuje. Jeśli dostanie trochę gorsze (qs ma pesymistyczną złożoność \(\displaystyle{ n^2}\)), będzie działał koszmarnie długo, albo wywali się z ambitnym komentarzem w stylu stack overflow.
-
- Użytkownik
- Posty: 971
- Rejestracja: 27 wrz 2005, o 22:56
- Płeć: Mężczyzna
- Lokalizacja: Wrocław
- Podziękował: 11 razy
- Pomógł: 75 razy
zliczanie bitów
Chyba mówisz o metodzie quickjuzef.
Są na to metody, np.: wariant wyboru mediany z trzech elementów,
plus wersja iteracyjna i takie tam drobiazgi.
Sprawdzałem to kiedyś: dla około miliona elementów maksymalny 'stos' jest równy
log(mln)*4=80 bajty, dla miliarda będzie 30*4 = 120 bytes. Dużo?
Jest jeszcze metoda zwana sortowaniem stogowym, też rzędu nlog(n).
Są na to metody, np.: wariant wyboru mediany z trzech elementów,
plus wersja iteracyjna i takie tam drobiazgi.
Sprawdzałem to kiedyś: dla około miliona elementów maksymalny 'stos' jest równy
log(mln)*4=80 bajty, dla miliarda będzie 30*4 = 120 bytes. Dużo?
Jest jeszcze metoda zwana sortowaniem stogowym, też rzędu nlog(n).