We wcześniejszych notkach pisałem o technologii SSE. Krótki opis, wsparcie ze strony kompilatora – czas więc na bardziej praktyczne podejście do tematu. W artykule tym skupię się jednak na działaniu instrukcji, która nie jest związana bezpośrednio z technologią SSE, a mianowicie na CPUID - proste, bezargumentowe polecenie, pozwalające uzyskać wiele przydatnych informacji na temat procesora.
Pisząc program korzystający z zestawu instrukcji SIMD, chcemy czasem zachować zgodność ze starszymi modelami procesorów, które nie wspomagają konkretnej wersji SSE lub 3DNow!. Wykorzystując także najnowsze wersje rozszerzeń multimedialnych (aktualnie jest to 4.1), musimy mieć na uwadze to, że wielu użytkowników nie posiada jeszcze procesorów, które je potrafią obsłużyć. Aby nie ograniczać liczby potencjalnych odbiorców, musimy więc zaopatrzyć się w metody, które wykonają stosowne obliczenia w sposób tradycyjny, lub za pomocą niższych wersji SSE. Problemem pozostaje oczywiście pobranie informacji o tym, jaki typ funkcji możemy wykonać na procesorze użytkownika. Rozwiązanie jest wbrew pozorom dość proste. Przyjrzyjmy się kilku z nich.
Jeśli interesuje nas lista podstawowych możliwości, możemy skorzystać z funkcji udostępnianych przez Windows Api: IsProcessorFeaturePresent i GetSystemInfo. Pierwsza z nich zwraca informacje o wersji obsługiwanych SSE (niestety, tylko do wersji trzeciej), MMX, 3DNow! a także garść innych danych (choćby dostępność Physical Address Extension). Kolejna z wymienionych funkcji, może być szczególnie przydatna, bo za jej pomocą można dowiedzieć się, ile fizycznych procesorów jest aktualnie zainstalowanych w systemie. Pobiera także dane dotyczące m.in. rozmiaru strony w pamięci operacyjnej.
Skorzystanie z powyższej funkcjonalności jest bardzo proste, zaś przykłady są na stronach ich dokumentacji, więc nie będę prezentował ich użycia.
Sprawdzanie jednak, czy procesor posiada obsługę MMX, w dzisiejszych czasach, nie jest już koniecznie wymagane – od ładnych kilku lat, należy to do standardowej funkcjonalności procesorów rodziny x86. Co jednak, gdy chcemy skorzystać z udogodnień SSE 4.1? Jego obsługa dziś nie jest jeszcze tak oczywista, podobnie jak SSSE 3. Tu z pomocą przychodzi nam assembler.
Instrukcja CPUID dostępna jest od rodziny procesorów Pentium i późniejszych Intel486. Nie wszystkie funkcje były wspomagane od razu, jednak od czasów Pentium 4, sytuacja nie zmieniła się zbyt mocno.
Ogólna idea działania jest bardzo prosta: do rejestru EAX przekazujemy numer funkcji (operacje jaką chcemy wykonać), wywołujemy instrukcję CPUID i dostajemy interesujące nas informacje w odpowiednich rejestrach. Na początek pobierzmy nazwę producenta – jest to 12 znakowy łańcuch, zapisany w 3 rejestrach:
char szName[13]; _asm{ mov eax,0x00000000; cpuid; mov DWORD PTR szName,ebx mov DWORD PTR szName+4,edx mov DWORD PTR szName+8,ecx } szName[12] = 0; printf("%s\n",szName);Na procesorach Intela zostanie wyświetlone: GenuineIntel, zaś AMD: AuthenticAMD.
Jak to działa? Bardzo prosto.
Pierwsza instrukcja mov zapisuje do rejestru EAX numer funkcji (w tym przypadku jest to funkcja o numerze 0). Następnie wywołujemy CPUID, które pobiera numer funkcji z rejestru EAX i wykonuje odpowiednią operacje. Funkcja, którą przekazaliśmy, zwraca wspomniany już identyfikator producenta procesora, w porcjach czterobajtowych, w rejestrach EBX, EDX, ECX. Aby dostać się do niego, przekazujemy do instrukcji mov wskaźnik na tablicę szName (warto przypomnieć, że nazwa tablicy jest jednocześnie wskaźnikiem jej początku) i nazwę konkretnego rejestru – ważne aby zastosować zaprezentowaną kolejność by dostać prawidłowo “poskładany” łańcuch. Pod przekazany adres tablicy kopiowana jest zawartość czterobajtowych rejestrów, więc, żeby uzupełnić tablicę, zwiększamy wskaźnik o ilość skopiowanych dotychczas danych (czyli właśnie 4 bajty) dla rejestrów EDX i ECX. Nie było to więc zbyt trudne zadanie. Dowiedzmy się więc, coś na temat SSE. Numer funkcji, za pomocą której dowiemy się o wspieranych przez procesor wersjach SSE, to 0x0000001, zaś interesujące nas informacje (czyli Standard Feature Flags) będą się znajdować w rejestrach EDX i ECX.
unsigned s1, s2; _asm{ mov eax,0x00000001; cpuid; mov s1,edx; mov s2,ecx; }
Zmienne s1 i s2 zostały wykorzystane tu jako wektory bitowe – każdy z 32 bitów przechowuje wartość 0 lub 1 (właściwość jest lub nie jest dostępna). W rejestrze EDX (więc u nas w zmiennej s1) przechowywane są informacje na temat dostępności MMX (bit 23), SSE (bit 25), SSE2 (bit 26). Czy jest obsługiwane SSE w wersji trzeciej dowiemy się sprawdzając bit zerowy zmiennej s2, SSSE3 bit 9 a SSE4.1 bit 19. Jak to zrobimy? Oczywiście posługując się operatorem >> (jego działanie i typowe zastosowanie opisał Xion, w swoim megatutorialu).
Ogólny schemat wygląda następująco:
((zmienna >> numer_bitu) & 1) ? printf("jest") : printf("nie ma");
Czyli, jeśli chcemy sprawdzić dostępność SSE2, piszemy:
((s1 >> 26) & 1 )? printf("jest\n") : printf("nie ma\n");
Dla bitu zerowego (SSE3), nie musimy wywoływać operatora >>.
Możemy także ubezpieczyć się, przed (czysto teoretyczną) sytuacją braku wsparcia dla CPUID. W takim przypadku, gdy spróbujemy wykonać tą instrukcję, wyrzucany jest wyjątek, zaś naszym zadaniem jest przechwycenie go.
__try{ _asm{ //... cpuid //... } } __except(EXCEPTION_EXECUTE_HANDLER) { if (_exception_code() == STATUS_ILLEGAL_INSTRUCTION){ printf("Instrukcja nie jest wspierana przez procesor. "); } }W tym przypadku mamy możliwość złapania wyjątku systemowego, a taki zostanie wyrzucony, jeśli spróbujemy użyć instrukcji nie wspieranej przez nasz procesor. Tak samo można zrobić dla instrukcji SIMD, jednak moim zdaniem nie jest to eleganckie. W przypadku CPUID, taki kod “opakowujący” jest użyty na wszelki wypadek i jego wykonanie jest praktycznie znikome, zaś dla SSE szanse te znacznie rosną i powinniśmy wykryć odpowiednie właściwości znacznie wcześniej.
Dokładny opis CPUID znajdziemy oczywiście w podręcznikach Intela, szczególnie w IA-32 Intel Architecture Software Developer’s Manual VOLUME 2A: Instruction Set Reference, A-M, stronach 3-179 do 3-208. Warto wspomnieć, że za pomocą tej instrukcji, możemy dowiedzieć się bardzo wielu niskopoziomowych szczegółów na temat procesora.
Brak komentarzy:
Prześlij komentarz