2012-02-19 22 views
7

Potrzebuję funkcji zmiennej __m128i, która ma okres 2^128. Nie trzeba monotonicznie zwiększać (jak licznik), ale odwiedzaj każdą wartość raz.128-bitowy licznik SSE?

Najprostszy przykład, jaki mogłem wymyślić, to w rzeczywistości 128-bitowy licznik, ale okazało się, że jest to trudne do wdrożenia w SSE. Czy są jakieś prostsze/szybsze rozwiązania?

+2

Dlaczego musisz odwiedzać 2^128 wartości? Żaden komputer na Ziemi nie jest w stanie tego zrobić. Nie możesz użyć 64-bitowej int? – usr

+0

Zgadzam się, na procesorze z zegarem w kolejności gigaherców, można konsumować jedną liczbę w każdym cyklu przez około 584 lat, zanim licznik 64-bitowy zostanie wyczerpany. – Damon

Odpowiedz

5

Oto monotoniczny licznik. Nie jestem pewien, czy możesz to nazwać prostym.

Zakładając, że zarówno ONE, jak i ZERO są zawsze w rejestrach, powinno to zostać skompilowane do 5 instrukcji. (7 lub 8, jeśli VEX-kodowanie nie jest używany)

inline __m128i nextc(__m128i x){ 
    const __m128i ONE = _mm_setr_epi32(1,0,0,0); 
    const __m128i ZERO = _mm_setzero_si128(); 

    x = _mm_add_epi64(x,ONE); 
    __m128i t = _mm_cmpeq_epi64(x,ZERO); 
    t = _mm_and_si128(t,ONE); 
    t = _mm_unpacklo_epi64(ZERO,t); 
    x = _mm_add_epi64(x,t); 

    return x; 
} 

Code Test (MSVC):

int main() { 

    __m128i x = _mm_setr_epi32(0xfffffffa,0xffffffff,1,0); 

    int c = 0; 
    while (c++ < 10){ 
     cout << x.m128i_u64[0] << " " << x.m128i_u64[1] << endl; 
     x = nextc(x); 
    } 

    return 0; 
} 

wyjściowa:

18446744073709551610 1 
18446744073709551611 1 
18446744073709551612 1 
18446744073709551613 1 
18446744073709551614 1 
18446744073709551615 1 
0 2 
1 2 
2 2 
3 2 

Nieco lepsza wersja sugerowane przez @ Norbert P. Zapisuje 1 instrukcję nad moim oryginalnym rozwiązaniem.

inline __m128i nextc(__m128i x){ 
    const __m128i ONE = _mm_setr_epi32(1,0,0,0); 
    const __m128i ZERO = _mm_setzero_si128(); 

    x = _mm_add_epi64(x,ONE); 
    __m128i t = _mm_cmpeq_epi64(x,ZERO); 
    t = _mm_unpacklo_epi64(ZERO,t); 
    x = _mm_sub_epi64(x,t); 

    return x; 
} 
+0

Dzięki, twój kod jest znacznie czystszy niż mój. Poczekam trochę, żeby sprawdzić, czy istnieją rozwiązania inne niż liczniki. – jk4736

+0

Pytanie brzmi, czy jest to naprawdę szybsze od rozwiązania nie używającego żadnego SSE. Chodzi mi o to, że oczywiste rozwiązanie wykorzystujące strukturę 2 64-bitowych unsigneds i 1 branch uniknie ewentualnego obciążenia SSE, a gałęzie będą bardzo przewidywalne. – Voo

+0

@ Voo Prawdopodobnie zależy to od tego, w jakiej formie ta wartość jest potrzebna.Jeśli jest to potrzebne w rejestrach ogólnych lub w pamięci, to 'add + adc' będzie najszybsze. Jeśli jest to potrzebne w rejestrze SSE, wówczas 5 instrukcji SSE-Int będzie prawdopodobnie szybsze niż jakikolwiek ekstrakt/insert. Przejście przez pamięć przez struct/union prawdopodobnie utknie w buforze load/store, ponieważ uzyskujesz dostęp do tej samej pamięci o innym rozmiarze słowa. – Mysticial

4

Nigdy nie zapominaj o zasadzie KISS.

wklejanie to (bez znaku liczby całkowite wymagane są do owijania wokół norma C, a tym samym wchodzenia na poszczególne wartości raz)

__uint128_t inc(__uint128_t x) { 
    return x+1; 
} 

język this wydajnością (x64)

addq $1, %rdi 
    adcq $0, %rsi 
    movq %rdi, %rax 
    movq %rsi, %rdx 
    ret 

łatwo/wystarczająco szybko? Jeśli inline, że prawdopodobnie będziesz w stanie uciec tylko z addq/adcq (The movq si ret są wymagane przez x64 ABI: jeśli inline funkcję, nie są one wymagane)


aby rozwiązać komentarz Voo chodzi o suckiness z MSVC, można użyć następujących:

inline void inc(unsigned long long *x, unsigned long long *y) { 
    if (!++*x) ++*y; // yay for obfuscation! 
} 

nie mają MSVC zainstalować w pobliżu, więc nie mogę go przetestować, ale powinien plastyczności coś podobnego do co napisałem powyżej. Następnie, jeśli chcesz naprawdę potrzebujesz __m128i, powinieneś być w stanie cast dwie połówki.

+0

Problem z tym rozwiązaniem polega na tym, że ładowanie i zapisywanie do rejestrów SSE zajmie dużo czasu. – jk4736

+0

W dużym stopniu zależy od kompilatora, jeśli ten kod jest prawidłowy. Jestem pewien, że nie jest to pod MSVC - __m128 to tylko struktura unii mniejszych prymitywów. – Voo

+0

@ jk4736 wszystkie rejestry w moim wycinku to non-SSE o szerokości 64-bitowej ... – CAFxX