Programowanie współbieżne, Języki programowania, Aplikacje, Gry

Następca OpenCL zapowiedziany!

Czerwiec 19th, 2011 Marcin Borowiec

Pamiętacie mój ostatni wpis o OpenCL? Pisałem w nim, że nie widzę OpenCL jako technologii przyszłości do programowania GPGPU. Przewidywałem także, że pojawi się nowa technologia, która ułatwi sposób tworzenia oprogramowania na GPU i wykorzysta przyszłe układy CPU i GPU. Przewidywania okazały się bardzo trafne i dzisiaj mogę wam powiedzieć jak ta technologia nazywa.

Tak więc po kolei:

I. Jak ta technologia się nazywa?

C++ AMP

II. Kto ją zapowiedział?

Herb Sutter w imieniu firmy Microsoft (w której pracuje) podczas konferencji organizowanej przez firmę AMD.

III. Co to jest C++ AMP?

Jest to otwarta specyfikacja rozszerzeń do języka C++ ver2011 (C++0x powinien zostać oficjalnie zatwierdzony w tym roku) zaproponowana przez Microsoft. Specyfikacja zawiera opis zestawu podzbiorów pełnego C++ i dodatkowej biblioteki z algorytmami i kontenerami. W obecnej chwili Microsoft mówi o jednym podzbiorze języka – podzbiorze pozbawionym m.in części operacji na wskaźnikach zgodnym z wymaganiami DirectCompute (elementu biblioteki DirectX 11). W przyszłej wersji Visual Studio Microsoft dostarczy dwie wersje kompilatora zgodnego z C++ AMP: domyślny pod CPU i w wersji restrict(direct3d) pod GPU. Kod pod CPU będzie wykonywał się natywnie, natomiast kod pod GPU będzie wykonywany poprzez bibliotekę DirectX 11. AMD zamierza stworzyć także swój własny kompilator pod C++ AMP (za pewne rozszerzy, któryś z open source’owych kompilatorów) i udostępni go pod systemy Windows, Linux i inne. Kompilator obsłuży kolejne generacje układów od AMD.

IV. Jak C++ AMP odnosi się do moich „5 punktów” z postu o OpenCL?

1. Ad: Możliwości zapisania programu w języku wysokiego poziomu bez sztucznego podziału: kod po stronie CPU, kod po stronie GPU

Kod tworzy się w języku C++ przy pomocą bibliotek, które znajdują się w specyfikacji C++ AMP. Poszczególne fragmenty kodu mogą zostać oznaczone jako restrict(nazwa_podzbioru). W zależności od poziomu restrykcji taki kod będzie się mógł uruchomić na różnych typach układów. Warto pamiętać, że kolejne generacje GPU będą coraz mniej restrykcyjne i w pewnym momencie mogą obsługiwać język C++ w pełni. Restrict nie mówi na jakim konkretnie układzie program ma działać a jaki zestaw funkcji taki układ musi posiadać. W tej chwili pełny zestaw zadziała tylko na CPU a zestaw direct3d na CPU i GPU (zarówno od AMD jak i Nvidii). Idea restrict jest znacznie szersza i po więcej informacji odsyłam do podlinkowanego wcześniej video z prezentacji C++ AMP. OpenCL zakładał, że GPU jest akceleratorem, który trzeba obsłużyć z poziomu kodu wykonywanego przez CPU (zainicjować, wysłać mu dane, uruchomić program, pobrać dane). C++ AMP zakłada, że posiadamy system z różnymi typami jednostek obliczeniowych i typami podsystemów pamięci (system heterogeniczny) a podział zadań pomiędzy tymi jednostkami, może być realizowany niejawne poprzez platformę C++ AMP. Jest to rozwiązanie bardziej ogólnie i sprawdzi się dużo lepiej przy kolejnych generacjach CPU i GPU.

2. Ad: Jak najniższego kosztu dostosowania obecnego oprogramowania do uruchomienia na GPU.

Programy pisze się w języku C++ więc jest to dużo ułatwienie, gdyż bardzo duża ilość oprogramowania została stworzona w tym języku. Niestety nie możemy oczekiwać, że koszt przeniesienia oprogramowania na C++ AMP będzie zerowy. Aby przenieść takie oprogramowanie czasem potrzebne będzie zastosowanie bardziej ogólnego mechanizmu do rozdzielania zadań czy użycie algorytmu, który lepiej skaluje się po rozdzieleniu na większą ilość wątków. Są to jednak rzeczy, które nie da się ominąć. Najważniejsze jest to, że nie mamy wielu sztucznych ograniczeń, które ma OpenCL.

3. Ad: Wykorzystania potencjału przyszłych układów: Kolejne wersje układów od Nvidii i AMD, rozwiązania Intela z rodziny Knights (Knights Ferry i Knights Corner).

O tym już pisałem w punkcie 1. C++ AMP ma bardziej przyszłościowy model programowania. Inną kwestią pozostaje to jak Nvidia i Intel odniosą się do tej technologii.

4. Ad: Wykorzystania potencjału jaki przyniosą nowe modele sterowników, chodzi tu m.in o WDDM 2.1 (Windows Display Driver Mode), który pojawi się prawdopodobnie w Windows 8.

W przyszłości zarządzanie zadaniami i pamięcią na GPU będzie odbywać się podobnie jak teraz na CPU. C++ AMP świetnie wpisuje się w ten model.

5. Ad: Narzędzi: debuggery, profilery itp które obsługiwać będzie się podobnie do tych, które obecnie używane są dla programów pisanych pod CPU.

Takie narzędzia ma posiadać następna wersja Visual Studio, łącznie z debuggerem kodu na GPU.

 

GPGPU ewoluuje i razem z nim podejście AMD-ATI. Za czasów Radeonów serii HD2xxx, HD3xxx, HD4xxx ATI udostępniało platformą opartą o kompilator Brook++. Ideą Brook++ było wykorzystanie GPU jako prostego procesora strumieniowego. To rozwiązanie przestało się sprawdzać razem z premierą Radeonów serii HD 5xxx (już przy Radeonach HD 4xxx słabo się sprawdzało), które posiadały wiele funkcji, z których nie można było skorzystać za pomocą Brook++. AMD porzuciło wtedy Brook++ i swoją uwagę skupiło na OpenCL. Tylko, że OpenCL też niedługo przestanie się sprawdzać. Prawdopodobnie już od Radeonów serii HD8xxx AMD będzie potrzebować czegoś więcej (C++ AMP) niż OpenCL.

Pozostaje pytanie co zrobi w tej sytuacji Nvidia. Jej CUDA było lepsze od Brook++, OpenCL zdobyło dość dużą popularność ale nie nadszarpnęło zbytnio pozycji platformy CUDA. Nvidia nawet olała implementacje OpenCL w wersji 1.1. Tylko, że teraz przeciwnik jest na prawdę duży. Co innego walczyć z firmą, która przeznacza znacznie mniejsze pieniądze na GPGPU (AMD kiedyś), a co innego walczyć z firmą, której heterogeniczne maszyny (kolejne generacje APU) są obecnie priorytetem nr 1. (AMD dziś) a pomaga jej firma Microsoft.

Volatile a publikacja obiektu w C++

Grudzień 5th, 2009 Marcin Borowiec

W jednym z poprzednich wpisów pisałem o problemie publikacji obiektu w programie wielowątkowym. Podałem wtedy bardzo prosty przykład kodu w pseudo asemblerze. Z kolei w  języku C++ publikacja mogła by wyglądać następująco:

bool gotowe = false;
Object *object = 0;
// ...
object = new Object(a,b,c);
gotowe = true;

Jak już wiemy abyśmy mieli gwarancje że object będzie dostępny jak tylko zmienna gotowe zmieni wartość na true, musimy upewnić się, że zapisy nie zostaną przeniesione przed inne zapisy i odczyty nie zostaną przeniesione za inne odczyty. W przypadku języków C# czy Java coś takiego możemy wymusić za pomocą deklaracji gotowe jako volatile. Niestety albo na szczęście standard C++ nic takiego nie przewiduje. W takim razie co należy zrobić aby uzyskać możliwość publikowania obiektu?

  • Jeśli nasz program będzie uruchamiany wyłącznie na procesorach x86 możemy zadeklarować gotowe jako volatile. To w zupełności wystarczy, gdyż model tej architektury jest stosunkowo mocny (Po szczegóły odsyłam do moich poprzednich wpisów).
  • Jeśli nasz program będziemy kompilować wyłącznie w Visual C++ 2005 lub nowszym, podobnie jak w poprzednich punkcie mamy gwarancje że volatile zadziała. Także dla kompilacji pod IA64. Jest to specjalna właściwość kompilatora Microsoftu.
  • W pozostałych przypadkach musimy albo opakować odwołania do gotowe w sekcję krytyczną albo użyć specjalistycznej biblioteki, z funkcją która wymuszą odpowiednie memory barrier. (Co prowadzi do oszczędności czasu procesora w stosunku do sekcji krytycznej).

Odnosząc się do słówka volatile pozostają jeszcze dwa pytania. Czy w takim razie volatile jest prawie całkiem bezużyteczne? oraz co o volatile mówi przyszły standard C++ 0x? Na te pytania postaram się odpowiedzieć, w któryś z kolejnych postów.

Trójkąt w DirectX 11

Październik 27th, 2009 Marcin Borowiec

Chwilowo odbiegnę od głównego tematu mojej strony czyli programowania współbieżnego. Niedawno potrzebowałem przygotować małą aplikacje z wykorzystaniem DirectX. Ponieważ nie miałem narzuconej wersji, postanowiłem sprawdzić najnowszy DirectX 11. O ile do wersji 10 w DirectX SDK znajduje się dość konkretny tutorial, o tyle do jedenastki mamy pusty projekt albo kilka konkretniejszych aplikacji. To czego ja potrzebowałem to szablon aplikacji wyświetlającej zwykły trójkąt. Postanowiłem że po prostu przerobie szybko aplikacje z lekcji 2 i 3 tutoriala do DirectX 10. Okazało się że sprawa nie jestem całkiem prosta więc stwierdziłem że podzielę się tutaj napotkanymi przeze mnie problemami i ich rozwiązaniami.

Wszystkie testy przeprowadzałem na Windows 7 x64 i DirectX SDK August 2009. DirectX 11 będzie dostępny na systemie Windows Vista za pomocą platform update. Nie sprawdzałem postępu prac nad platform update i nie jestem w stanie powiedzieć czy aktualna wersja pozwoli uruchomić aplikacje DirectX 11.

Przerabiając aplikacje wyświetlającą trójkąt z dziesiątki na jedenastke trzeba zdać sobie sprawę z kilku rzeczy:

  • DirectX 11 wprowadza nowy model programowania wielowątkowego. W tym celu interfejs ID3D11Device został rozdzielony na dwa niezależne interfejsy ID3D11Device i i ID3D11DeviceContext. Dzięki temu drugiemu możliwe jest generowanie komend renderowania grafiki z wielu wątków.
  • D3DXMath został zastąpiony przez XnaMath. Zamiast np. D3DXVECTOR3 można użyć XMFLOAT3. XnaMath dostępne jest po dołączeniu xnamath.h
  • Effects są teraz dostępne jako projekt vc++ w katalogu DX SDK\Utilities\Source\Effects11. Należy je skompilować, najlepiej od razu wszystkie 4 warianty (x86 – x64, Debug – Release) aby później dołączyć header i plik lib do programu wyświetlającego trójkąt.
  • Nie istnieje funkcja do jednoczesnej kompilacji kodu hlsl i stworzenia efektu. Teraz należy najpierw skompilować shadery a później utworzyć effecty.
  • Kompilator shaderów obsługuje tylko format fx_5_0. Aby stworzyć kod zgodny z kartami DirectX 10 należy w definicji techniki przekazać odpowiedni parametr, np vs_4_0, ps_4_0.

To by było w zasadzie wszystko co jest potrzebne do wyświetlenia trójkąta w DirectX gdyby nie pewien mały problem. Przy obecnej wersji Effects w DX SDK  nie jest możliwe poprawne uruchomienie takiej aplikacji na karcie zgodnej z co najwyżej z DX10.1 (Z tego co mi wiadomo na Radeonach HD5xxx wszystko chodzi bardzo ładnie 😉 ) Problem jest opisany tutaj. Podane są tam dwa przykładowe rozwiązania. W skrócie podczas wywołania D3DX11CreateEffectFromMemory wywoływane są funkcje Create…Shader interfejsu ID3D11Device. Jednym z parametrów tych funkcji jest ID3D11ClassLinkage *pClassLinkage. Parametr ten w zależność od levelu: D3D_FEATURE_LEVEL_11_0 lub inny powinien zawierać poprawny wskaźnik lub być wartością NULL. Osobiście aby u siebie uzyskać działający profil D3D_FEATURE_LEVEL_10_0 i D3D_FEATURE_LEVEL_10_1 zamieniłem linie 1272 i 1273 w pliku EffectNonRuntime.cpp w projekcie Effects11 z:

else if( FAILED( (m_pDevice->*(pShader->pVT->pCreateShader))((UINT *) pShader->pReflectionData->pBytecode, pShader->pReflectionData->BytecodeLength,m_pClassLinkage, &pShader->pD3DObject) ) )
                pShader->IsValid = FALSE;

na

else if( FAILED( (m_pDevice->*(pShader->pVT->pCreateShader))((UINT *) pShader->pReflectionData->pBytecode, pShader->pReflectionData->BytecodeLength, 0, &pShader->pD3DObject) ) )
               pShader->IsValid = FALSE;

Należy pamiętać że w ten sposób wyłączam level 11_0. Dla mnie to w tej chwili nie jest problemem ponieważ testuje ten program tylko u siebie na sprzęcie zgodnym z DX10. Jeśli jednak potrzebujemy mieć działające wszystkie profile, możemy np. dodać parametr do tej funkcji i przekazać jawnie na którym levelu operujemy i wtedy dodać ifa wywołującego CreateShader z classlinkage albo bez lub np próbować tworzyć shader najpierw z classlinkage a w razie nie powodzenia bez.

Na koniec załączam zip z projektem stworzonym  w Visual Studio 2010 beta2 wyświetlającym piękny żółty trójkąt. Program działa u mnie poprawnie na karcie AMD (ATI) zgodnej z DX10.1 i NVIDII zgodnej z DX10. W przypadku próby kompilacji załączonego kodu w poprzednich wersjach VS wystarczy stworzyć projekt win32 C++, zaimportować pliki .h i .cpp, dodać w pliku dx11.h po includach

#define nullptr 0

(nullptr to taki ładny null z VC++ 10), upewnić się że vs ma dostęp do odpowiednich headerów i libów i dodać linkerowi liby: d3d11.lib d3dx11d.lib D3DX11EffectsD.lib D3DCompiler.lib w przypadku trybu Debug i d3d11.lib d3dx11.lib D3DX11Effects.lib D3DCompiler.lib w przypadku trybu Release.

DX11Triangle (3242)

d3d11.lib
d3dx11d.lib
D3DX11EffectsD.lib
D3DCompiler.lib

MIT OpenCourseWare

Sierpień 10th, 2009 Marcin Borowiec

Ostatnio trafiłem na stronę bezpłatnych kursów publikowanych przez uczelnię MIT (Massachusetts Institute of Technology). Listę kursów można obejrzeć pod adresem: http://ocw.mit.edu/OcwWeb/web/courses/courses/index.htm. Gorąco polecam przejrzenie tej strony. Jest bardzo dużo różnych tematów, zarówno z dziedziny informatyki jak i innych.

Mnie szczególnie zainteresował kurs Multicore Programming Primer. Jak sama nazwa mówi jest on poświęcony programowaniu systemów wielordzeniowych. W ramach kursu dostępne są slajdy z prezentacji, video z prezentacji, przykładowe kody źródłowe programów czy quizy, które pomogą sprawdzić ile zapamiętaliśmy i zrozumieliśmy. W Multicore Programming Primer opisano m.in.:

  • Postawy związane z programowaniem systemów wielordzeniowych.
  • Procesor Cell (zawarty m.in. w konsoli PlayStation3). Procesor ten jest bardzo ciekawym przykładem, gdyż jego budowa wymusza określony sposób rozdzielania zadań na wątki czy komunikacji między wątkami. Wymaga od programisty zastanowienia się nad rzeczami, nad którym w ogóle nie trzeba zaprzątać sobie głowy pisząc program np pod Core 2 Duo czy Phenoma X4.
  • Języki programowania tworzone z myślą o procesorach wielordzeniowych: Cilk (rozszerzenie C) czy dość nietypowy StreamIt. Osobiście uważam że programowanie wielowątkowe powinno być integralną częścią języka, a nie dodatkiem w postaci zewnętrznych bibliotek (tak jak to jest w C++). Co więcej program jednowątkowy powinien być szczególnym przypadkiem programu wielowątkowego. Obecnie większość programów pisze się dokładnie odwrotnie.
  • Podstawy tworzenia kompilatorów, które automatycznie rozdzielają wykonanie kodu na wiele wątków. Ten temat szczególnie mnie zainteresował.

Volatile a operacje atomowe w C++

Lipiec 29th, 2008 Marcin Borowiec

O volatile pisałem już w „programowaniu wielowątkowych gier w języku C++”. Niestety opis, który tam przedstawiłem jest dość pobieżny. Postanowiłem więc powrócić do tego tematu i opisać volatile dokładniej.

Tłumacząc z angielskiego volatile to „zmienny, niestabilny”. volatile w C++ jest kwalifikatorem typu a jego działanie jest określone w standardzie C++ jako: „volatile is a hint to the implementation to avoid aggressive optimization involving the object because the value of the object might be changed by means undetectable by an implementation.” Co oznacza mniej więcej że kompilator powinien zrezygnować z optymalizacji i przy każdym odwołaniu do tej zmiennej wczytać nową wartość z komórki pamięci.

Tylko czy to wystarcza aby zmienne volatile mogły służyć do synchronizacji wątków?

W tym poście opisze kwestie wykorzystania volatile do uzyskania operacji atomowych (A raczej tego że volatile z operacjami atomowymi nie ma nic wspólnego).  W kolejnych wpisach przedstawię inne próby wykorzystania volatile do synchronizacji wątków.

Interesują nas operacje atomowe na liczbach całkowitych, o rozmiarze nie większym niż rejestry ogólnego przeznaczenia. Operacja atomowa to operacja, która zostaje wykonywana nieprzerwalnie dla każdego wątku, procesu. Wyobraźmy sobie dwa wątki które inkrementują zmienną a:

int a = 0;
Wątek1: a++;         Wątek2: a++;

Przy stanie początkowym a wynoszącym 0 nie mamy żadnej pewności że po wykonaniu tych operacji a będzie miało wartość 2. Odwołując się do poprzedniego postu o widoku pamięci dla wątku przyczyną tego mogą być m.in optymalizacje kompilatora. Kompilator nie ma pojęcia że inny wątek też chce modyfikować tą samą zmienną. W efekcie każdy wątek może operować na lokalnej kopii zmiennej:

A co będzie jeśli zmienną a zadeklarujemy jako volatile?

Visual C++ 2005 w trybie Release z wyłączonymi optymalizacjami (-O0) instrukcję a++ przetworzył na 3 instrukcje x86 (załadowanie wartości do rejestru, inkrementacja na rejestrze, zapis do pamięci). Skoro procesor operuje na rejestrze to dalej każdy z wątków posiada własny niezsynchronizowany widok pamięci. Jak widać volatile nie wystarcza do uzyskania poprawnych operacji atomowych (Przykładowy możliwy przebieg programu przedstawiłem w „Programowanie wielowątkowych gier w języku C++”). Na szczęście coraz więcej programistów zdaje sobie z tego sprawę. Do niedawna panowała dość duża dezinformacja, która powstała głównie przez to że dla domyślnych ustawień komplacji (np Visual C++, Release -O2) instrukcja a++; była kompilowana do pojedynczej instrukcji add operującej bezpośrednio na pamięci. Na jednordzeniowych komputerach taki program wykonywał się zawsze poprawnie co mogło sprawiać wrażenie że takie konstrukcję są poprawne. Należy jednak pamiętać że

  • Kompilator może posłużyć się pomocniczym rejestrem do wykonania operacji. Opis volatile mówi tylko o każdorazowym pobraniu wartości z pamięci przy odwołaniu do zmiennej. Nie zabrania tymczasowego użycia rejestru.
  • Nawet jeśli kompilator wygeneruje instrukcję „add na pamięci” to na systemie wielordzeniowym dalej to nie jest poprawna instrukcja atomowa. Instrukcje a++; mogą być wykonane przez każdy z wątków równocześnie. Dodatkowo wynik operacji trafia najpierw do „write buffer” skąd nie jest widoczny dla innych wątków. Ze względu na wydajność procesory nie rozwiązują tych dwóch problemów automatycznie. Konieczne jest wykonanie maszynowej instrukcji „lock”, która nie jest dodawana przez żaden z kompilatorów.

Do tematu volatile i operacji atomowych powróce jeszcze w przyszłości.

Widok wątku na pamięć

Lipiec 15th, 2008 Marcin Borowiec

Na wstępie zachęcam do przeczytania artykułu, o którym pisałem w poprzednim poście. Osobom nie zorientowanym w temacie programowania wielowątkowego znacząco ułatwi zrozumienie tego co będę chciał poniżej przedstawić.

Aby zrozumieć co oznacza „widok pamięci wątku” warto przeanalizować krótki program w języku C++:

int a;
// --- instrukcje ---
a += 2;
a += 3;

Zakładamy:

  • a jest zmienną, dla której została zaalokowana komórka w pamięci komputera
  • wartość tej komórki przed wykonaniem instrukcji (3) wynosi 0 (tzn że wartość a wynosi 0)
  • tylko jeden wątek wykonuje kod przedstawiony powyżej, żaden inny wątek nie modyfikuje a
  • rozpatrujemy program napisany w języku C++, skompilowany i uruchomiony na popularnych procesorach rodziny x86 (Athlon 64, Core 2, Pentium itp …)

Należy się zastanowić kiedy wynik instrukcji (3) zostanie zapisany do komórki pamięci komputera i dlaczego należy się tym przejmować. Są ku temu 4 powody:

  1. Kompilator zoptymalizował kod i postanowił połączyć intrukcje (3) i (4) w jedną instrukcję a+=5; Z punktu widzenia programu w języku C++ jest to równoznaczne. W efekcie, do pamięci może nigdy nie zostać zapisana wartość 2, pojawi się dopiero wartość 5.
  2. Kompilator postanowił zapisać wartość sumy z instrukcji (3) do rejestru procesora. Pozwoli to na zoptymalizowanie wykonania kolejnych instrukcji na zmiennej a. W końcu zapis i odczyt z rejestru jest znacznie szybszy niż odwołania do pamięci. W przedstawionym przypadku zapis do pamięci może się odbyć po wykonaniu instrukcji (3) i (4) jako skopiowanie wyniku z rejestru procesora do pamięci. Podobnie jak w pkt 1. w komórce pamięci może nastąpić zmiana bezpośrednio z wartości 0 na 5.
  3. „Kompilator wysyła” do pamięci wynik wykonania instrukcji (3) czy to poprzez instrukcję add operującą bezpośrednio na pamięci czy to poprzez sekwencję instrukcji add na rejestrze i move czyli przeniesienia wartości z rejestru do pamięci. Problem w tym że te operacje w rzeczywistości nie powodują bezpośredniego zapisu do pamięci komputera. Procesor także posiada mechanizmy, które mają zwiększyć szybkość wykonywania programów. W rzeczywistości nowa wartość trafia najpierw do „write buffer” gdzie czeka na przesłanie dalej, podczas gdy procesor zajmuje się już wykonaniem kolejnych instrukcji. Write buffer to bardzo mały bufor pamięci. Jak tylko jest możliwość przesłania danych do cache procesora opuszczają one write buffer.
  4. Pamięć procesora jest przeważnie znacznie wolniejsza niż sam procesor a samo przesłanie danych wiąże się z dość dużymi opóźnieniami. Z tego powodu procesor został wyposażony w zintegrowaną pamięć zwaną pamięcią cache. Ostatnio używane dane są buforowane w tej pamięci przez co znacznie skraca się ponowny czas dostępu do nich. Również z powodu optymalizacji nowe dane najpierw zostają zapisane do pamięci cache, a dopiero gdy jest taka potrzebna trafiają do pamięci RAM komputera. Na szczęście nie musimy się martwić jak to dokładnie działa. Synchronizacja pamięci cache odbywa się w całości na poziomie sprzętowym, według protokołów MESI i MOESI (w zależności od procesora). Jeśli procesor potrzebuje dane, które znajdują się w pamięci cache innego procesora to zostaną przesłane do tego pierwszego bez żadnej programowej ingerencji.

O ile o optymalizacjach kompilatora i istnieniu pamięci cache procesora wiedzą chyba prawie wszyscy programiści o tyle niewiele osób zdaje sobie sprawę z istnienia „write bruffera”. Mam takie wrażenie przeglądając kody źródłowe niektórych programów i czytając artykuły o programowaniu wielowątkowym w C++. Zwracam szczególną uwagę na to że pamięć cache to co innego niż write buffer, to pierwsze jest synchronizowane sprzętowo, to drugie jest synchronizowane programowo.

Wracając do tytułu posta. Każdy z wątków wykonywanych w ramach jednego procesu może inaczej widzieć w danym momencie pamięć komputera. Różnice wychodzą właśnie z tymczasowych wartości, które znajdują się w rejestrach czy write bufferze. Należy oczywiście zwrócić uwagę na fakt że stan pamięci cache nie wpływa na widok wątku. Jak już wcześniej napisałem synchronizacja pamięci cache odbywa się w całości sprzętowo niewidocznie dla programisty.

Dopóki korzystamy z funkcji pokroju EnterCryticalSection, InterlockedIncrement nie musimy w ogóle zdawać sobie sprawy z tego o czym tutaj napisałem. Jeśli jednak chcemy zrozumieć jak te funkcje działają, chcemy napisać podobne funkcje albo po prostu chcemy nasz program zoptymalizować warto rozumieć te mechanizmy. W następnych postach będę wielokrotnie odwoływał się do tego artykułu, żeby wyjaśnić kiedy pewne optymalizacje są poprawne a kiedy nie i dlaczego.

Programowanie wielowątkowych gier w języku C++

Lipiec 7th, 2008 Marcin Borowiec

Dziś dodałem pierwszy artykuł do działu publikacje.

„Programowanie wielowątkowych gier w języku C++” zawiera m.in:

  • Podstawy teoretyczne dotyczące synchronizacji wątków
  • Opis jednych z wielu metod synchronizacji wątków w języku C++
  • Zwrócenie uwagi na często pojawiające się błędy w programowaniu w języku C++ (volatile, itp …).
  • Przedstawienie modelu pamięci programowania aplikacji w C++  (niezbędne do zrozumienia zasad pisania poprawnych programów)
  • Krótki zarys OpenMP
  • Uwagi dla programistów gier

Bezpośredni link do artykułu: pwgwjcpp