Archive for 2008

Wyrównanie pamięci dla operatorów new i new[]

Gdy programuje się coś z użyciem funkcji wewnętrznych kompilatora (Intrinsics) dla SSE, najlepiej jest gdy to, co do nich przekazujemy jest wyrównanie do 16 bajtów, w przeciwnym wypadku powstaje problem z odczytaniem z pamięci i program się wysypuje. Oczywiście można korzystać z funkcji ładujących, które przyjmują dane niewyrównane, ale funkcje te są z oczywistych względów wolniejsze. Do ustawienia wyrównania danych pól stosuję się specjalną deklaracje __declspec(align(16)), którą należy umieścić przed definicją zmiennej (oczywiście dotyczy to Microsoft Visual Studio).

__declspec(align(16)) __m128 m_vector;

Niestety taka deklaracja dotyczy tylko obiektów na stosie. Te, które są tworzone na stercie, czyli przez operator new oraz operator new[], nie zawsze są wyrównane, dlatego trzeba zadbać o to samemu odpowiednio przeciążając te funkcje. Przeciążanie tych operatorów nie jest trudne. Oba przyjmują jako argument ilość potrzebnego miejsca wyrażoną w bajtach, a zwracają wskaźnik typu void, który zawiera adres odpowiedniego obszaru pamięci. Nagłówek jednego z tych operatorów wygląda następująco:

void* operator new(const size_t nBytes)

W ciele takiej funkcji należy zaalokować odpowiednią ilość miejsca, a następnie wyrównać wskaźnik tak, aby dzielił się przez 16. Dopiero ten wskaźnik można zwrócić. Należy oczywiście pamiętać o zapisaniu gdzieś poprzedniego wskaźnika oraz o alokacji pamięci tak, żeby po wyrównaniu nie wychodzić poza przydzielony zakres.
W przypadku mojej funkcji adres jest zapisany przed wyrównanym adresem (dzięki poradom kolegów z forum ), a ilość alokowanego miejsca jest większa o rozmiar alokowanej struktury + 3 bajty.

void* operator new(const size_t nBytes)
{
 char* ptr = new char[nBytes + sizeof(NVector) + 3];
 unsigned int temp = reinterpret_cast<unsigned int>(ptr);  // Konwersja na postać odpowiednią do obliczeń
 unsigned int t = temp % 16;   // Obliczam o ile jest przesunięty od wyrównania
 temp += (t &lt; 13) ? (16 - t) : (32 - t);  // Dodaje odpowiednie przesunięcie do wskaźnika
 void* aPtr = reinterpret_cast<void*>(temp); // Zapis nowego wskaźnika
 temp -= sizeof(void*);
 *reinterpret_cast<unsigned int*>(temp) = reinterpret_cast<unsigned int>(ptr); // Zapis poprzedniego wskaźnika
 return aPtr;
}

NVector jest klasą, w której umieściłem ten operator. Liczba 13 występująca przy porównywaniu w powyższym kodzie reprezentuje przesunięcie, dla którego nie jest możliwe zapisanie wskaźnika przed najbliższym wyrównaniem, dlatego trzeba dodać więcej. Operator new[]wygląda identycznie, dlatego można w nim wywołać powyższą funkcję. Odpowiednik delete dla tego operatora wygląda następująco:

void operator delete(void* p)
{
	unsigned int temp = reinterpret_cast<unsigned int>(p) - sizeof(void*);
	char* temp2 = reinterpret_cast<char*>(*reinterpret_cast<int*>(temp));
	delete[] temp2;
}

Powyższa funkcja oblicza poprzedni wskaźnik i korzystając z niego dealokuje obszar. Operator delete[] jest identyczny.

Niskopoziomowa zabawa

Zabawa w kodzie na niskim poziomie może być bardzo fajna, ja porównuję ją do rozwiązywania łamigłówek (myślę, że są osoby, które podzielają moje zdanie :)). Czasem ta zabawa sprowadza się zbadania, czy zmiana jednej funkcji assemblerowej powoduje przyspieszenie kodu o ten jeden cykl, chociaż jest to już skrajność. Przykładem tego może być funkcja obliczająca długość wektora 4D wykorzystująca funkcje wewnętrzne kompilatora – Intrinsics:

float Length()
{
   float f;
   __m128 temp = _mm_mul_ps(m_vector, m_vector);
   __m128 temp2 = _mm_add_ps(_mm_movehl_ps(temp, temp), temp);
   temp = _mm_shuffle_ps(temp2, temp2, _MM_SHUFFLE(0,0,0,1));
   _mm_store_ss(&f, _mm_sqrt_ss(_mm_add_ss(temp2, temp)));
   return f;
}

Powyższy kod jest najszybszą wersją jaką udało mi się uzyskać. Polega on na obliczeniu kwadratów wszystkich składowych, dodaniu ich do siebie oraz zpierwiastkowanie. Najbardziej pracochłonną częścią jest tutaj wbrew pozorom dodawanie, gdyż kwadrat składowych można załatwić jednym mnożeniem wektorowym. Dodawanie jednak rozbija się na dwie operacje dodawania oraz dwie operacje przemieszania, ponieważ SSE nie oferuje funkcji, która dodawałaby wszystkie elementy pojedynczego rejestru. Do wykonywania operacji przemieszania najczęściej stosuje się funkcje _mm_shuffle(), ponieważ pozwala ona dowolnie rozmieścić elementy w rejestrze. Rozszerzenie SSE umożliwia jednak wykonanie przemieszań za pomocą innych funkcji (_mm_unpackhi_ps(), _mm_unpacklo_ps(), _mm_movehl_ps(), _mm_movelh_ps()), które mieszają elementy tylko w określony sposób. W powyższym kodzie zastosowałem właśnie _mm_unpackhi(), ponieważ funkcja zapisuje elementy x i y na pozycji elementow z i w.

Inną sprawą jest, w jaki sposób kompilator radzi sobie z takim kodem. Otóż posiada on specjalnie zaprogramowane optymalizacje dla tego typu funkcji oraz typu __m128. Kompilator widząc te funkcje zamienia je na wywołania odpowiednich funkcji w kodzie assemblera, przy czym sam zarządza wykorzystaniem rejestrów xmm. Nie trzeba się zatem martwić o ilość zmiennych typu __m128, ponieważ nie wpływa to bezpośrednio na rejestry.

Fenomen SSE

Na warsztacie pojawił się ostatnio bardzo ciekawy temat dotyczący wykorzystania instrukcji procesora przetwarzających dane potokowo. Jak wiadomo każdemu programiście, każdy procesor wspiera różny zestaw funkcji (ponieważ są one dołączane do poprzednich). O to lista tych rozszerzeń:

  • MMX (operacje na liczbach całkowitych)
  • SSE (operacje zmiennoprzecinkowe pojedynczej precyzji)
  • SSE2 (operacje zmiennoprzecinkowe podwójnej precyzji)
  • SSE3 (konwersje i dodawanie w poziomie)
  • SSSE3 (dodatkowe działania na liczbach całkowitych)
  • SSE4 (dodatkowe instrukcje wektorowe, w tym upragniony iloczyn skalarny dwóch wektorów)

O ile rozszerzenie MMX korzysta z rejestrów koprocesora (co czyni go niewydajnym, ponieważ przełączanie między MMX a koprocesorem trwa trochę czasu), o tyle pozostałe rozszerzenia wprowadzają dodatkowe rejestry xmm (8 dla procesorów 32-bitowych oraz 16 dla procesorów 64-bitowych) po 128 bitów każdy. Rejestry te są traktowane tak jak jest to wymagane do danej funkcji, zatem dla liczb całkowitych jest całe 128-bitowe słowo lub można dzielić na pół, aż do uzyskania 16 osobnych bajtów. W przypadku liczb zmiennoprzecinkowych są to 4 liczby typu float lub 2 liczby typu double.

Dostępne funkcje pozwalają wykonywać obliczenia zarówno skalarnie jak i wektorowo. Oczywiście najlepiej jest gdy przeprowadzane operacje są głównie wektorowe, ponieważ pozwala to zaoszczędzić mnóstwo cennych cykli procesora. Największy problem to oczywiście pisanie operacji przy użyciu tych funkcji, ponieważ wymagana jest podstawowa znajomość Assemblera, chociaż dzięki tzw. funkcjom intrinsics wcale nie trzeba robić w kodzie wstawek asemblerowych, gdyż funkcje robią to zamiast programisty.

Przyznam szczerze, mnie również  bardzo interesują te funkcje, dlatego postanowiłem z ich pomocą napisać własną implementacje wektorów i macierzy (również dlatego, że te w D3DX, nie mają tego wsparcia). Jest to dość żmudna robota, ponieważ trzeba jakoś zagwarantować to, żeby moje wektory ruszyły na starszych komputerach (a nuż trzeba będzie), ale mimo wszystko uważam, że warta zachodu. Mogę już powiedzieć, że funkcja obliczająca długość wektora, dzięki instrukcją z rozszerzenia SSE, jest szybsza od wersji z D3DX.

Ubuntu – nowa zabawka

Któż by mógł przypuszczać, że Linux jest taką fajną zabawką. Okazuje się jednak, że ten system operacyjny może sprawić dużo frajdy. Ostatnio dużo czasu spędzam na Ubuntu i muszę stwierdzić, że jestem z niego zadowolony. Wybrałem tę dystrybucję, ponieważ jest bardzo prosta w obsłudze, a konkurencyjna Fedora miała problemy z instalacją. Sam system jest 64-bitowy, gdyż chciałem zobaczyć różnicę między nim, a wersją 32-bitową, którą miałem wcześniej. Wrażenie jest czysto subiektywne (wydaje się lepiej). Ze sterownikami nie było żadnych problemów, ponieważ Ubuntu zawiera wszystkie, jakie są potrzebne.

Wygląd Ubuntu można bardzo prosto dostosować, wystarczy tylko aby w systemie znalazł się dekorator okien Emerald oraz Compiz-Fusion. Dzięki temu tandemowi można Linuksa przerobić z wyglądu na Windowsa, a nawet jeszcze lepiej. Mój system wygląda jak na zdjęciu :).Wyglad

Inną rzeczą, która mnie w Linuksie zainsteresowała, to możliwość uruchamiania aplikacji windowsowych, szczególnie że udało mi się załapać na darmową wersję aplikacji CrossOver. Osobiście korzystam z dwóch wersji: Linux i Games, dlatego mogę przyznać, że są to bardzo dobre aplikacje.

NLib

Postanowiłem udostępnić moją “bibliotekę”, którą posługuję się od jakiegoś czasu. Nie jest ona jakoś super wypasiona, ale zawiera wszystkie elementy, które są mi aktualnie potrzebne, czyli:

  • Moduł podstawowy – konwersje, typedefy, jakieś funkcje liczące w czasie kompilacji
  • Profile
  • Logger
  • FStream – strumień dla plików w oparciu o funkcje WinApi
  • StructReader – funkcja do obsługi bardzo prostych plików konfiguracyjnych
  • Timer – obsługa zegara, licznik FPS w oparciu o QPC lub GetTickCount()
  • Window – prosta klasa do obsługi okna

Update’y pojawiają się wraz z pomysłami i zapotrzebowaniem na nowe wynalazki, więc nie wiem kiedy nowa wersja.

Pobierz

Podstawy programowania

W czwartek 2 października odbyły się pierwsze zajęcia laboratoryjne z Podstaw Programowania na Politechnice Wrocławskiej. Językiem programowania na zajęciach jest Java. Nie ma w niej wskaźników, dlatego dla wielu osób jest dużo łatwiejsza niż C++. Ja myślę, że może być to ciekawe doświadczenie i przydatna umiejętność, bo jak wiadomo, najlepiej znać wiele języków.

Największym moim zaskoczeniem na zajęciach okazało się “IDE”, ponieważ ciężko mi nazwać BlueJ środowiskiem programistycznym. Jest to raczej zabawka dla początkujących, która pokazuje w jaki sposób działają funkcje, lecz niestety z poważnym programowaniem ma niewiele wspólnego.  Na stanowisku znalazłem również środowisko Eclipse, więc pomimo, że jeszcze go nie znam, jest nadzieja na coś lepszego.

Studia

Witam ponownie jako student Politechniki Wrocławskiej wydziału Informatyki i Zarządzania kierunku Informatyka. Jak wiadomo wakacje wakacjami i nie ma czasu na nic, dlatego zająłem się projektem trójwymiarowej gry Tetris. Niestety dotarłem do ślepego zaułku, a główną przyczyną tego okazał się bardzo wciągający serial Stargate SG-1 + filmy oraz Stargate Atlantis. Stargate SG-1 jest serią 10 sezonów,  każdy składający się z ok. 20 odcinków po 40 minut każdy, więc jest to ogromna ilość czasu jaką należy poświecić.  Wracając jednak do tematu tego bloga muszę stwierdzić, że ciężko jest mi pracować nad obecnym projektem, więc myślę o rozpoczęciu czegoś nowego.

Małe programy

Postanowiłem udostępnić dwie pomocne aplikacje, które były mi potrzebne kiedyś, więc sobie je na szybko skodziłem w ramach treningu. Oto one:

  • FileListing – aplikacja zapisuje ponumerowaną listę plików w katalogu, w którym została uruchomiona
  • NewLineFix -aplikacja naprawia wszystkie znaki nowej linii dodając do nich \r, przydatna przy czytaniu plików pisanych pod linuksem.

Mam nadzieję, że komuś się przydadzą :).

Kontrola rodzicielska

Kontrola rodzicielska w systemie Windows Vista, znana jako UAC, jest mechanizmem chroniącym komputer przed uruchamianiem niebezpiecznych programów. Za każdym razem, gdy użytkownik próbuje uruchomić aplikację, która ingeruje w rejestr, bądź próbuje zapisać coś na dysku UAC prosi o potwierdzenie wykonania tej czynności.

Z początku odnosiłem się do tego sceptycznie, ponieważ za każdym razem gdy coś robiłem, wyskakiwało okienko z prośbą o potwierdzenie. Dlatego też w pewnym momencie wyłączyłem tą usługę. Ostatnio jednak się nad nią ulitowałem i ją uruchomiłem. Teraz muszę przyznać, że jest warta tych komunikatów, ponieważ mając na pendrive’ie wirusa, sposób jest zarazić Vistę z uruchomionym UACiem, wyskakuje piękny komunikat odnośnie jego uruchomienia.

Pomimo, tego że na ludzką głupotę lekarstwa nie ma, polecam uruchomienie Kontroli rodzicielskiej w systemie, gdyż dzięki temu można przynajmniej dowiedzieć się nieco o niebezpieczeństwie czyhającym w programach :).

Paczka

Właśnie opublikowałem paczkę dotychczasowej pracy. Nie jest tego dużo, ale zawsze coś. Muszę przyznać, że ostatnio pogrywam sobie w L2 więc czasu trochę mniej. Paczka zawiera prostą aplikację wykorzystującą mój menadżer scen, dzięki któremu można przełączać dowolnie między scenami. Więcej w pliku ReadMe.
NineTester

Pobierz