DZWONEK

Są tacy, którzy czytają te wiadomości przed tobą.
Subskrybuj, aby otrzymywać świeże artykuły.
E-mail
Imię
Nazwisko
Jak chcesz przeczytać Dzwon
Bez spamu

saul 9 września 2015 o 13:38

Implementowanie wielowątkowej architektury silnika gry

  • Blog Intel,
  • Produkcja gier
  • Programowanie równoległe
  • Tworzenie stron internetowych
  • Transfer

Wraz z pojawieniem się procesorów wielordzeniowych stało się konieczne stworzenie silnika gry opartego na architekturze równoległej. Zastosowanie wszystkich procesorów systemowych - zarówno graficznych (GPU), jak i centralnych (CPU) - otwiera znacznie więcej możliwości w porównaniu do silnika jednowątkowego opartego tylko na procesorach graficznych. Na przykład, używając większej liczby rdzeni procesora, możesz poprawić efekty wizualne poprzez zwiększenie liczby fizycznych obiektów używanych w grze, a także osiągnąć bardziej realistyczne zachowanie postaci poprzez wdrożenie zaawansowanej sztucznej inteligencji (AI).
Rozważ funkcje implementacji wielowątkowej architektury silnika gry.

1. Wstęp

1.1 Przegląd

Wielowątkowa architektura silnika gry pozwala maksymalnie wykorzystać możliwości wszystkich procesorów platformy. Polega na równoległym wykonywaniu różnych bloków funkcjonalnych na wszystkich dostępnych procesorach. Okazuje się jednak, że wdrożenie takiego schematu nie jest takie proste. Poszczególne elementy silnika gry często współdziałają ze sobą, co może prowadzić do błędów, gdy są wykonywane w tym samym czasie. Aby przetworzyć takie scenariusze, silnik zapewnia specjalne mechanizmy synchronizacji danych, które wykluczają możliwe blokady. Implementuje także metody jednoczesnej synchronizacji danych, dzięki czemu czas wykonania można zminimalizować.

Aby zrozumieć prezentowane materiały, musisz być dobrze zaznajomiony z nowoczesnymi metodami tworzenia gier komputerowych, obsługi wielowątkowości w silnikach gier lub ogólnie w celu poprawy wydajności aplikacji.

2. Stan równoległego wykonania

Równoległy stan wykonania jest kluczową koncepcją wielowątkowości. Tylko dzieląc silnik gry na osobne systemy, z których każdy działa we własnym trybie i praktycznie nie wchodzi w interakcje z resztą silnika, możemy osiągnąć największą wydajność przetwarzania równoległego i skrócić czas wymagany do synchronizacji. Nie można całkowicie odizolować poszczególnych części silnika, z wyjątkiem wszystkich typowych zasobów. Jednak w przypadku takich operacji, jak uzyskiwanie danych dotyczących pozycji lub orientacji, poszczególne systemy mogą wykorzystywać lokalne kopie danych zamiast współdzielonych zasobów. Pomaga to zminimalizować zależność danych w różnych częściach silnika. Powiadomienia o zmianach w danych ogólnych dokonywane przez oddzielny system są wysyłane do menedżera stanu, który umieszcza je w kolejce. Nazywa się to trybem przesyłania wiadomości. Ten tryb zakłada, że \u200b\u200bpo zakończeniu zadań układy silnika otrzymają powiadomienia o zmianach i odpowiednio zaktualizują swoje dane wewnętrzne. Taki mechanizm może znacznie skrócić czas synchronizacji i zależność systemów od siebie.

2.1 Statusy

Aby menedżer stanu wykonywania działał wydajnie, zaleca się synchronizację operacji na określonym zegarze. Dzięki temu wszystkie systemy mogą działać jednocześnie. Jednak częstotliwość taktowania nie musi odpowiadać liczbie klatek na sekundę. Czas trwania zegarów może nie zależeć od częstotliwości. Można go wybrać tak, aby jeden cykl zegara odpowiadał czasowi wymaganemu do przesłania jednej ramki (niezależnie od jego wielkości). Innymi słowy, częstotliwość lub czas trwania cykli zależy od konkretnej implementacji menedżera stanu. Rysunek 1 pokazuje „darmowy” tryb pracy krok po kroku, w którym nie jest wymagane, aby wszystkie systemy zakończyły operację w jednym i tym samym cyklu zegara. Tryb, w którym wszystkie systemy wykonują operacje w jednym cyklu, nazywany jest „trudnym” trybem krok po kroku. Jest to schematycznie przedstawione na ryc. 2.


Rysunek 1. Status wykonania w wolnym trybie przyrostowym

2.1.1 Darmowy tryb krok po kroku
W wolnym trybie krok po kroku wszystkie systemy działają w sposób ciągły przez z góry określony czas niezbędny do wykonania następnej partii obliczeń. Jednak nazwy „wolny” nie należy brać dosłownie: systemy nie są zsynchronizowane w żadnym momencie, są one jedynie „wolne” w wyborze liczby cykli zegara wymaganych do następnego etapu.
Z reguły w tym trybie nie wystarczy wysłać menedżerowi stanu prostego powiadomienia o zmianie stanu. Konieczne jest również przesyłanie zaktualizowanych danych. Wynika to z faktu, że system, który zmienił udostępnione dane, może być w toku, podczas gdy inny system, który czeka na te dane, jest gotowy do przeprowadzenia aktualizacji. W takim przypadku wymagana jest więcej pamięci, ponieważ musisz utworzyć więcej kopii danych. Dlatego „wolnego” systemu nie można uznać za uniwersalne rozwiązanie na każdą okazję.
2.1.2 Trudny tryb krok po kroku
W tym trybie wykonywanie zadań wszystkich systemów odbywa się w jednym kroku. Taki mechanizm jest łatwiejszy do wdrożenia i nie wymaga przesyłania zaktualizowanych danych wraz z powiadomieniem. Rzeczywiście, jeśli to konieczne, jeden system może po prostu zażądać nowych wartości od innego systemu (oczywiście pod koniec cyklu wykonania).
W trybie trudnym można zaimplementować pseudo-wolny tryb działania krok po kroku, rozdzielając obliczenia między poszczególne etapy. W szczególności może to być wymagane do obliczeń AI, gdzie początkowy „wspólny cel” jest obliczany dla pierwszego cyklu zegara, który jest stopniowo udoskonalany w kolejnych krokach.


Rysunek 2. Status wykonania w trybie twardego przyrostu

2.2 Synchronizacja danych

Zmiana współdzielonych danych w wielu systemach może prowadzić do konfliktu zmian. W takim przypadku system przesyłania komunikatów musi udostępnić algorytm wyboru poprawnej wartości całkowitej. Istnieją dwa główne podejścia oparte na następujących kryteriach.
  • Czas: Ostateczna zmiana staje się wartością końcową.
  • Priorytet: Ostateczna wartość to zmiana dokonana przez system o najwyższym priorytecie. Jeśli priorytet systemów jest taki sam, możesz również wziąć pod uwagę czas zmiany.
Wszystkie nieaktualne dane (według dowolnego kryterium) można po prostu zastąpić lub wykluczyć z kolejki powiadomień.
Ponieważ końcowa wartość może zależeć od kolejności wprowadzania zmian, użycie względnych wartości całkowitych danych może być bardzo trudne. W takich przypadkach należy zastosować wartości bezwzględne. Następnie, podczas aktualizacji danych lokalnych, systemy mogą po prostu zastąpić stare wartości nowymi. Najlepszym rozwiązaniem jest wybór wartości bezwzględnych lub względnych w zależności od konkretnej sytuacji. Na przykład dane ogólne, takie jak pozycja i orientacja, muszą mieć wartości bezwzględne, ponieważ kolejność zmian jest dla nich ważna. Wartości względne można zastosować na przykład w systemie generowania cząstek, ponieważ wszystkie informacje o cząstkach są w nim przechowywane.

3. Silnik

Podczas opracowywania silnika nacisk kładziony jest na elastyczność niezbędną do dalszego rozszerzenia jego funkcjonalności. To zoptymalizuje go do użycia w warunkach określonych ograniczeń (na przykład z pamięci).
Silnik można warunkowo podzielić na dwie części: ramę i menedżerów. Ramy (patrz sekcja 3.1) obejmują części gry, które są replikowane podczas wykonywania, czyli istnieją w kilku egzemplarzach. Zawiera także elementy związane z realizacją głównego cyklu gry. Menedżerowie (patrz sekcja 3.2) są obiektami Singleton odpowiedzialnymi za wykonanie logicznego komponentu gry.
Poniżej znajduje się schemat silnika gry.


Rysunek 3. Ogólna architektura silnika

Należy pamiętać, że funkcjonalne moduły lub systemy gier nie są częścią silnika. Silnik tylko je łączy, działając jako element łączący. Taka modułowa organizacja umożliwia ładowanie i rozładowywanie systemów w razie potrzeby.

Interakcja silnika i układów odbywa się za pomocą interfejsów. Są one realizowane w taki sposób, aby zapewnić silnikowi dostęp do funkcji systemów, a systemów - menedżerom silnika.
Szczegółowy schemat silnika przedstawiono w dodatku A, „Schemat silnika”.

W rzeczywistości wszystkie systemy są od siebie niezależne (patrz sekcja 2, „Stan jednoczesnego wykonania”), tzn. Mogą wykonywać działania równolegle, bez wpływu na działanie innych systemów. Jednak każda zmiana danych pociąga za sobą pewne trudności, ponieważ systemy będą musiały ze sobą współdziałać. Wymiana informacji między systemami jest konieczna w następujących przypadkach:

  • informowanie innego systemu o zmianie współdzielonych danych (na przykład pozycji lub orientacji obiektów);
  • do wykonywania funkcji, które nie są dostępne dla tego systemu (na przykład system AI odnosi się do systemu do obliczania właściwości geometrycznych lub fizycznych obiektu w celu przeprowadzenia testu przecięcia promienia).
W pierwszym przypadku możesz użyć menedżera stanu opisanego w poprzedniej sekcji do zarządzania wymianą informacji. (Aby uzyskać więcej informacji na temat menedżera stanu, patrz sekcja 3.2.2, „Menedżer stanu”).
W drugim przypadku konieczne jest wdrożenie specjalnego mechanizmu, który pozwoli świadczyć usługi jednego systemu do korzystania z innego. Pełny opis tego mechanizmu znajduje się w rozdziale 3.2.3, „Service Manager”.

3.1 Struktura

Rama służy do łączenia wszystkich elementów silnika. Inicjuje silnik, z wyjątkiem menedżerów, których instancje są tworzone globalnie. Przechowuje również informacje o scenie. Aby osiągnąć większą elastyczność, scena jest realizowana jako tak zwana scena uniwersalna, która zawiera obiekty uniwersalne. Są to pojemniki, które łączą różne funkcjonalne części sceny. Więcej informacji znajduje się w sekcji 3.1.2.
Główny cykl gry jest również wdrażany w ramach. Schematycznie można to przedstawić w następujący sposób.


Rysunek 4. Główna pętla gry

Silnik działa w środowisku okienkowym, dlatego na pierwszym etapie cyklu gry konieczne jest przetworzenie wszystkich niekompletnych komunikatów okien systemu operacyjnego. Jeśli nie zostanie to zrobione, silnik nie będzie odpowiadał na komunikaty systemu operacyjnego. W drugim kroku planista przypisuje zadania za pomocą menedżera zadań. Proces ten został szczegółowo opisany w sekcji 3.1.1 poniżej. Następnie menedżer stanu (patrz punkt 3.2.2) wysyła informacje o zmianach dokonanych w układach silnika, na których działanie może to mieć wpływ. W ostatnim kroku, w zależności od statusu wykonania, struktura określa, czy silnik powinien zostać ukończony, czy kontynuowany, na przykład, aby przejść do następnej sceny. Informacje o stanie silnika są przechowywane w menedżerze środowiska. Szczegółowe informacje można znaleźć w sekcji 3.2.4.

3.1.1 Planista
Program planujący generuje zegar wykonania odniesienia z określoną częstotliwością. Jeśli w trybie testu porównawczego wymagane jest, aby następna operacja rozpoczęła się natychmiast po zakończeniu poprzedniej, bez oczekiwania na zakończenie pomiaru, częstotliwość może być nieograniczona.
Zegar, korzystając z menedżera zadań, ustawia system w trybie uruchamiania. W wolnym trybie krok po kroku (sekcja 2.1.1) program planujący odpytuje systemy, aby ustalić, ile cykli zegara będzie potrzebnych do wykonania zadania. Na podstawie wyników ankiety harmonogram określa, które systemy są gotowe do wykonania, a które zakończą pracę w określonym rytmie. Program planujący może zmienić liczbę miar, jeśli jakikolwiek system potrzebuje więcej czasu na wykonanie. W trudnym trybie krok po kroku (sekcja 2.1.2) wszystkie systemy rozpoczynają i kończą wykonywanie w tym samym cyklu zegara, więc program planujący czeka na zakończenie wszystkich systemów.
3.1.2 Uniwersalna scena i przedmioty
Uniwersalna scena i obiekty to kontenery funkcjonalności zaimplementowane w innych systemach. Są przeznaczone wyłącznie do interakcji z silnikiem i nie wykonują żadnych innych funkcji. Można je jednak rozszerzyć, aby skorzystać z funkcji dostępnych dla innych systemów. Pozwala to na słabe sprzężenie. Rzeczywiście, uniwersalna scena i obiekty mogą korzystać z właściwości innych systemów bez przywiązywania się do nich. Ta właściwość eliminuje zależność systemów od siebie i umożliwia im jednoczesną pracę.
Poniższy schemat pokazuje rozszerzenie uniwersalnej sceny i obiektu.


Rysunek 5. Rozszerzenie uniwersalnej sceny i obiektu

Rozważ zasadę rozszerzeń na poniższym przykładzie. Załóżmy, że wykonywane jest uniwersalne uniwersalne rozszerzenie sceny, scena jest rozszerzana w celu wykorzystania właściwości graficznych, fizycznych i innych. W tym przypadku „graficzna” część rozszerzenia będzie odpowiedzialna za inicjalizację wyświetlacza, a jego „fizyczna” część będzie odpowiedzialna za wdrożenie praw fizycznych dla ciał stałych, takich jak grawitacja. Sceny zawierają obiekty, więc scena uniwersalna obejmie również kilka obiektów uniwersalnych. Obiekty ogólne mogą być również rozwijane i rozszerzane w celu wykorzystania właściwości graficznych, fizycznych i innych. Na przykład narysowanie obiektu na ekranie zostanie zaimplementowane z graficznymi funkcjami rozszerzania i obliczeniem interakcji brył z fizycznymi.

Szczegółowy schemat interakcji silnika i układów znajduje się w dodatku B, „Schemat interakcji silnika i układów”.
Należy zauważyć, że scena uniwersalna i obiekt uniwersalny są odpowiedzialne za rejestrację wszystkich swoich „rozszerzeń” w menedżerze stanu, aby wszystkie rozszerzenia mogły otrzymywać powiadomienia o zmianach wprowadzonych przez inne rozszerzenia (to znaczy inne systemy). Przykładem jest rozszerzenie graficzne zarejestrowane w celu otrzymywania powiadomień o zmianach pozycji i orientacji dokonanych przez rozszerzenie fizyczne.
Aby uzyskać więcej informacji o komponentach systemu, zobacz rozdział 5.2, „Komponenty systemu”.

3.2 Menedżerowie

Menedżerowie zarządzają silnikiem. Są to obiekty Singleton, co oznacza, że \u200b\u200bkażdy typ menedżera jest dostępny tylko w jednej instancji. Jest to konieczne, ponieważ powielanie zasobów menedżera nieuchronnie doprowadzi do nadmiarowości i negatywnie wpłynie na wydajność. Ponadto menedżerowie są odpowiedzialni za wdrażanie wspólnych funkcji dla wszystkich systemów.
3.2.1 Menadżer zadań
Menedżer zadań odpowiada za zarządzanie zadaniami systemowymi w puli wątków. Aby zapewnić optymalne n-krotnie skalowanie i zapobiec przypisywaniu niepotrzebnych wątków, eliminując nieuzasadnione koszty przełączania zadań w systemie operacyjnym, pula wątków tworzy jeden wątek dla każdego procesora.

Program planujący wysyła do menedżera zadań listę zadań do wykonania, a także informacje o zakończeniu zadań, na które trzeba czekać. Otrzymuje te dane z różnych systemów. Każdy system otrzymuje tylko jedno zadanie do wykonania. Ta metoda nazywa się rozkładem funkcjonalnym. Jednak w przypadku przetwarzania danych każde takie zadanie można podzielić na dowolną liczbę podzadań (rozkład danych).
Poniżej znajduje się przykład podziału zadań między wątkami dla systemu czterordzeniowego.


Rysunek 6. Przykładowa pula wątków używana przez menedżera zadań

Oprócz przetwarzania wniosków harmonogramu o dostęp do głównych zadań, menedżer zadań może pracować w trybie inicjalizacji. Kolejno odpytuje systemy z każdego wątku, aby mogły zainicjować lokalne magazyny danych niezbędne do działania.
Wskazówki dotyczące wdrażania menedżera zadań znajdują się w dodatku D, Wskazówki dotyczące wdrażania zadań.

3.2.2 Kierownik stanu
Menedżer stanu jest częścią mechanizmu przesyłania komunikatów. Śledzi zmiany i wysyła powiadomienia o nich do wszystkich systemów, na które zmiany te mogą mieć wpływ. Aby nie wysyłać niepotrzebnych powiadomień, menedżer stanu przechowuje informacje o tym, które systemy powiadomić w konkretnym przypadku. Mechanizm ten jest realizowany w oparciu o wzorzec obserwatora (patrz Dodatek C, „Obserwator (wzorzec projektowy)”). W skrócie, szablon ten obejmuje użycie „obserwatora”, który monitoruje wszelkie zmiany w temacie, podczas gdy kontroler zmian pełni rolę pośrednika między nimi.

Mechanizm działa w następujący sposób. 1. Obserwator informuje kontrolera zmian (lub kierownika stanu), które zmiany chce monitorować pod kątem badanych. 2. Podmiot powiadamia kontrolera o wszystkich swoich zmianach. 3. Na podstawie sygnału ramowego kontroler powiadamia obserwatora o zmianach w obiekcie. 4. Obserwator wysyła podmiotowi żądanie zaktualizowanych danych.

W darmowym trybie wykonywania krok po kroku (patrz sekcja 2.1.1) implementacja tego mechanizmu jest nieco bardziej skomplikowana. Najpierw zaktualizowane dane będą musiały zostać wysłane wraz z powiadomieniem o zmianie. W tym trybie wysyłanie na żądanie nie ma zastosowania. Rzeczywiście, jeśli w momencie otrzymania żądania system odpowiedzialny za zmiany nie ukończył jeszcze wykonywania, nie będzie w stanie dostarczyć zaktualizowanych danych. Po drugie, jeśli jakiś system nie jest jeszcze gotowy do odbierania zmian na końcu cyklu, menedżer stanu będzie musiał przechowywać zmienione dane, dopóki wszystkie zarejestrowane w nich systemy nie staną w stanie gotowości.

W ramach tego istnieją dwa menedżery stanu: do przetwarzania zmian na poziomie sceny i na poziomie obiektu. Zazwyczaj komunikaty dotyczące scen i obiektów są od siebie niezależne, więc użycie dwóch oddzielnych menedżerów eliminuje potrzebę przetwarzania niepotrzebnych danych. Ale jeśli konieczne jest uwzględnienie stanu obiektu na scenie, można go zarejestrować, aby otrzymywać powiadomienia o jego zmianach.

Aby nie wykonywać niepotrzebnej synchronizacji, menedżer stanu tworzy kolejkę powiadomień o zmianie oddzielnie dla każdego wątku utworzonego przez menedżera zadań. Dlatego podczas uzyskiwania dostępu do kolejki synchronizacja nie jest wymagana. W sekcji 2.2 opisano metodę, której można użyć do scalenia kolejek po wykonaniu.


Rysunek 7. Powiadomienie o wewnętrznych zmianach obiektu uniwersalnego

Powiadomienia o zmianach nie muszą być wysyłane sekwencyjnie. Istnieje sposób na przesłanie ich równolegle. Wykonując zadanie, system działa ze wszystkimi swoimi obiektami. Na przykład, gdy obiekty fizyczne oddziałują na siebie, system fizyczny kontroluje ich ruch, obliczanie kolizji, nowe siły itp. Po otrzymaniu powiadomienia obiekt systemu nie wchodzi w interakcje z innymi obiektami swojego systemu. Współdziała z powiązanymi z nim rozszerzeniami uniwersalnego obiektu. Oznacza to, że uniwersalne obiekty są teraz od siebie niezależne i mogą być aktualizowane w tym samym czasie. To podejście nie wyklucza skrajnych przypadków, które należy wziąć pod uwagę w procesie synchronizacji. Pozwala jednak na użycie równoległego trybu wykonywania, gdy wydawało się, że można działać tylko sekwencyjnie.

3.2.3 Kierownik serwisu
Menedżer usług zapewnia systemom dostęp do funkcji innych systemów, które w innym przypadku byłyby dla nich niedostępne. Ważne jest, aby zrozumieć, że funkcje są dostępne za pośrednictwem interfejsów, a nie bezpośrednio. Informacje o interfejsach systemowych są również przechowywane w menedżerze usług.
Aby wyeliminować zależność systemów od siebie, każdy z nich ma tylko niewielki zestaw usług. Ponadto o możliwości korzystania z określonej usługi decyduje nie sam system, ale menedżer usług.


Rysunek 8. Przykładowy menedżer usług

Menedżer usług ma inną funkcję. Zapewnia systemom dostęp do właściwości innych systemów. Właściwości to określone wartości określonych systemów, które nie są przesyłane w systemie przesyłania komunikatów. Może to być rozszerzenie rozdzielczości ekranu w systemie graficznym lub wielkość grawitacji w fizyce. Menedżer usług zapewnia systemom dostęp do takich danych, ale nie pozwala na ich bezpośrednią kontrolę. Umieszcza zmiany właściwości w specjalnej kolejce i publikuje je dopiero po wykonaniu sekwencyjnym. Należy pamiętać, że dostęp do właściwości innego systemu jest rzadko wymagany i nie należy go nadużywać. Na przykład może być potrzebny do włączenia i wyłączenia trybu szkieletowego w systemie graficznym z okna konsoli lub do zmiany rozdzielczości ekranu na żądanie odtwarzacza z interfejsu użytkownika. Ta funkcja służy głównie do ustawiania parametrów, które nie zmieniają się między ramkami.

3.2.4 Kierownik ds. Środowiska
  • Menedżer środowiska zapewnia środowisko wykonawcze silnika. Jego funkcje można warunkowo podzielić na następujące grupy.
  • Zmienne: nazwy i wartości wspólnych zmiennych używanych przez wszystkie części silnika. Zazwyczaj wartości zmiennych są określane podczas ładowania sceny lub niektórych ustawień użytkownika. Silnik i różne systemy mogą uzyskać do nich dostęp, wysyłając zapytanie.
  • Wykonanie: dane dotyczące wydajności, takie jak zakończenie sceny lub programu. Parametry te mogą być ustawiane i żądane zarówno przez sam system, jak i silnik.
3.2.5 Menedżer platformy
Menedżer platformy implementuje abstrakcję dla wywołań systemu operacyjnego, a także zapewnia dodatkową funkcjonalność oprócz prostej abstrakcji. Zaletą tego podejścia jest hermetyzacja kilku typowych funkcji w jednym wywołaniu. Oznacza to, że nie będą musiały być wdrażane osobno dla każdego dzwoniącego, obciążając go szczegółowymi informacjami na temat wywołań systemu operacyjnego.
Rozważmy na przykład wywołanie menedżera platformy w celu załadowania dynamicznej biblioteki systemowej. Nie tylko ładuje system, ale także odbiera punkty wejścia funkcji i wywołuje funkcję inicjalizacji biblioteki. Menedżer przechowuje również deskryptor biblioteki i zwalnia go po zakończeniu pracy silnika.

Menedżer platformy jest również odpowiedzialny za dostarczanie informacji o procesorze, na przykład o obsługiwanych instrukcjach SIMD, oraz za inicjowanie określonego trybu działania procesów. System nie może używać innych funkcji generowania zapytań.

4. Interfejsy

Interfejsy są środkami interakcji między strukturą, menedżerami i systemami. Struktura i menedżerowie są częścią silnika, dzięki czemu mogą bezpośrednio ze sobą współdziałać. Układy nie mają zastosowania do silnika. Ponadto wszystkie spełniają różne funkcje, co prowadzi do potrzeby stworzenia jednej metody interakcji z nimi. Ponieważ systemy nie mogą bezpośrednio komunikować się z menedżerami, muszą zapewnić inną metodę dostępu. Jednak nie wszystkie funkcje menedżera powinny być otwarte dla systemów. Niektóre z nich są dostępne tylko dla frameworka.

Interfejsy definiują zestaw funkcji wymaganych do korzystania ze standardowej metody dostępu. Eliminuje to potrzebę znajomości przez platformę szczegółów implementacji określonych systemów, ponieważ może ona wchodzić w interakcje z nimi tylko poprzez określony zestaw wywołań.

4.1 Interfejsy podmiotu i obserwatora

Głównym celem interfejsów podmiotu i obserwatora jest rejestracja, którzy obserwatorzy wysyłają powiadomienia o tym, które tematy, a także wysyłanie takich powiadomień. Rejestracja i zerwanie połączenia z obserwatorem to standardowe funkcje dla wszystkich podmiotów objętych implementacją ich interfejsu.

4.2 Interfejsy menedżera

Menedżerowie, chociaż są obiektami Singleton, są bezpośrednio dostępni tylko dla frameworka. Inne systemy mogą uzyskiwać dostęp do menedżerów tylko za pośrednictwem interfejsów, które stanowią tylko część ich ogólnej funkcjonalności. Po inicjalizacji interfejs jest przesyłany do systemu, który używa go do pracy z niektórymi funkcjami menedżera.
Nie ma jednego interfejsu dla wszystkich menedżerów. Każdy z nich ma osobny interfejs.

4.3 Interfejsy systemowe

Aby framework mógł uzyskać dostęp do komponentów systemu, potrzebuje interfejsów. Bez nich obsługa każdego nowego układu silnika musiałaby zostać wdrożona osobno.
Każdy system zawiera cztery elementy, dlatego powinny istnieć cztery interfejsy. Mianowicie: system, scena, obiekt i zadanie. Szczegółowy opis znajduje się w rozdziale 5, „Systemy”. Interfejsy są środkiem dostępu do komponentów. Interfejsy systemowe umożliwiają tworzenie i usuwanie scen. Z kolei interfejsy scen umożliwiają tworzenie i niszczenie obiektów, a także żądanie informacji o głównym zadaniu systemu. Interfejs zadań jest używany głównie przez menedżera zadań podczas ustawiania zadań w puli wątków.
Ponieważ scena i przedmiot, jako części systemu, muszą oddziaływać ze sobą oraz ze sceną uniwersalną i przedmiotem, do którego są przyłączone, ich interfejsy są również tworzone na podstawie interfejsów podmiotu i obserwatora.

4.4 Zmień interfejsy

Interfejsy te służą do przesyłania danych między systemami. Wszystkie systemy, które wprowadzają zmiany określonego typu, muszą implementować taki interfejs. Przykładem jest geometria. Interfejs geometrii obejmuje metody określania położenia, orientacji i skali elementu. Każdy system, który wprowadza zmiany w geometrii, musi implementować taki interfejs, aby dostęp do zmienionych danych nie wymagał informacji o innych systemach.

5. Systemy

Systemy są częścią silnika, który odpowiada za wdrożenie funkcji gier. Wykonują wszystkie podstawowe zadania, bez których silnik nie miałby sensu. Interakcja między silnikiem a układami odbywa się za pomocą interfejsów (patrz sekcja 4.3, „Interfejsy systemowe”). Jest to konieczne, aby nie przeciążać silnika informacjami o różnych typach systemów. Dzięki interfejsom proces dodawania nowego systemu staje się znacznie prostszy, ponieważ silnik nie musi uwzględniać wszystkich szczegółów implementacji.

5.1 Rodzaje

Układy silnika można podzielić na kilka predefiniowanych kategorii odpowiadających standardowym komponentom gry. Na przykład: geometria, grafika, fizyka (zderzenie brył), dźwięk, przetwarzanie wejściowe, sztuczna inteligencja i animacja.
Systemy z niestandardowymi funkcjami należą do osobnej kategorii. Ważne jest, aby zrozumieć, że każdy system, który zmienia dane określonej kategorii, powinien wiedzieć o interfejsie tej kategorii, ponieważ silnik nie zapewnia takich informacji.

5.2 Komponenty systemu

Dla każdego systemu musi być zaimplementowanych kilka komponentów. Oto niektóre z nich: system, scena, obiekt i zadanie. Wszystkie te elementy są wykorzystywane do interakcji z różnymi częściami silnika.
Poniższy schemat pokazuje interakcje między różnymi komponentami.


Rysunek 9. Elementy systemu

Szczegółowy schemat zależności między układami silnika znajduje się w dodatku B, „Schemat interakcji silnik-układ”.

5.2.1 System
Komponent „systemowy” lub po prostu system odpowiada za inicjalizację zasobów systemowych, które prawie się nie zmienią podczas pracy silnika. Na przykład system graficzny analizuje adresy zasobów, aby określić ich lokalizację i przyspieszyć ładowanie podczas korzystania z zasobu. Ustawia również rozdzielczość ekranu.
System jest głównym punktem wejścia do frameworka. Dostarcza informacji o sobie (na przykład rodzaju systemu), a także metod tworzenia i usuwania scen.
5.2.2 Scena
Komponent sceny lub scena systemowa odpowiada za zarządzanie zasobami związanymi z bieżącą sceną. Scena uniwersalna wykorzystuje sceny systemowe do rozszerzenia funkcjonalności za pomocą ich funkcji. Jako przykład możemy przytoczyć scenę fizyczną, która służy do stworzenia nowego świata gry i określa w nim siły grawitacyjne podczas inicjowania sceny.
Sceny zapewniają metody tworzenia i niszczenia obiektów, a także komponent „zadania” do przetwarzania sceny i metodę dostępu do niej.
5.2.3 Obiekt
Komponent „obiektowy” lub obiekt systemowy należy do sceny i zwykle jest powiązany z tym, co użytkownik widzi na ekranie. Obiekt ogólny używa obiektu systemowego w celu rozszerzenia funkcjonalności poprzez podanie swoich właściwości jako własnych.
Przykładem jest geometryczne, graficzne i fizyczne rozszerzenie uniwersalnego obiektu w celu wyświetlenia drewnianej belki na ekranie. Właściwości geometryczne będą obejmować pozycję, orientację i skalę obiektu. System graficzny użyje specjalnej siatki do jej wyświetlenia. A system fizyczny nada mu właściwości ciała stałego do obliczania interakcji z innymi ciałami i działającymi siłami grawitacji.

W niektórych przypadkach konieczne jest uwzględnienie zmian w obiekcie uniwersalnym lub jednym z jego rozszerzeń w obiekcie systemowym. W tym celu możesz utworzyć specjalny link, który pozwala śledzić wprowadzone zmiany.

5.2.4 Zadanie
Komponent zadania lub zadanie systemowe służy do przetwarzania sceny. Zadanie otrzymuje polecenie zaktualizowania sceny z menedżera zadań. Jest to sygnał do uruchomienia funkcji systemowych na obiektach sceny.
Wykonywanie zadań można podzielić na podzadania, rozdzielając je również za pomocą menedżera zadań na jeszcze większą liczbę wątków. Jest to wygodny sposób na skalowanie silnika do wielu procesorów. Ta metoda nazywa się rozkładem danych.
Informacje o zmianie obiektów w trakcie aktualizacji zadań sceny są przekazywane do menedżera stanu. Aby uzyskać więcej informacji na temat menedżera stanu, patrz sekcja 3.2.2.

6. Łączenie wszystkich składników

Wszystkie opisane powyżej elementy są ze sobą połączone i stanowią część jednej całości. Działanie silnika można podzielić na kilka etapów, opisanych w poniższych sekcjach.

6.1 Faza inicjalizacji

Silnik zaczyna się od inicjalizacji menedżerów i frameworka.
  • Struktura wywołuje moduł ładujący sceny.
  • Po ustaleniu, z których systemów będzie korzystała scena, moduł ładujący wywołuje menedżera platformy w celu załadowania odpowiednich modułów.
  • Menedżer platformy pobiera odpowiednie moduły i przekazuje je do menedżera interfejsu, a następnie wywołuje je w celu utworzenia nowego systemu.
  • Moduł zwraca do modułu ładującego wskaźnik do instancji systemu, która implementuje interfejs systemu.
  • Menedżer usług rejestruje wszystkie usługi oferowane przez moduł systemu.


Rysunek 10. Inicjalizacja menedżerów i układów silnika

6.2 Etap ładowania etapu

Kontrola jest zwracana do modułu ładującego, który ładuje scenę.
  • Moduł ładujący tworzy scenę uniwersalną. Do tworzenia scen systemowych przywołuje interfejsy systemowe, rozszerzając funkcjonalność sceny uniwersalnej.
  • Scena uniwersalna określa, jakie dane każda scena systemowa może zmienić, i ostrzega o tym, jakie zmiany powinna otrzymać.
  • Porównując sceny, które dokonują pewnych zmian i chcą otrzymywać powiadomienia o nich, scena uniwersalna przekazuje te informacje kierownikowi stanu.
  • Dla każdego obiektu sceny moduł ładujący tworzy obiekt uniwersalny, a następnie określa, które systemy rozwiną obiekt uniwersalny. Zgodność między obiektami systemu jest określana zgodnie z tym samym schematem, który jest używany dla scen. Jest również przekazywany do kierownika stanu.
  • Korzystając z uzyskanych interfejsów scen, moduł ładujący tworzy instancje obiektów systemowych i wykorzystuje je do rozszerzania obiektów uniwersalnych.
  • Program planujący żąda od interfejsów sceny danych o ich głównych zadaniach w celu przesłania tych informacji do menedżera zadań podczas procesu wykonywania.


Rysunek 11. Inicjalizacja uniwersalnej sceny i obiektu

6.3 Etap cyklu gry

  • Menedżer platformy służy do przetwarzania komunikatów okien i innych elementów niezbędnych do działania bieżącej platformy.
  • Następnie kontrola przechodzi do harmonogramu, który czeka na koniec zegara, aby kontynuować pracę.
  • Pod koniec pomiaru w wolnym trybie krok po kroku program planujący sprawdza, które zadania zostały wykonane. Wszystkie ukończone zadania (czyli gotowe do wykonania) są przekazywane do menedżera zadań.
  • Harmonogram określa, które zadania zostaną zakończone w bieżącym takcie i czeka na ich zakończenie.
  • W trybie trudnym kroki te operacje są powtarzane co każdy takt. Program planujący przenosi wszystkie zadania do menedżera i oczekuje ich wykonania.
6.3.1 Rozwiązanie zadania
Zarządzanie przechodzi do menedżera zadań.
  • Tworzy kolejkę od wszystkich otrzymanych zadań, a następnie, gdy pojawiają się wolne przepływy, rozpoczyna ich realizację. (Proces wykonywania zadań różni się w zależności od systemu. Systemy mogą pracować tylko z jednym zadaniem lub przetwarzać kilka zadań z kolejki jednocześnie, tym samym wdrażając wykonywanie równoległe).
  • W trakcie wykonywania zadań mogą pracować z całą sceną lub tylko z niektórymi obiektami, zmieniając swoje dane wewnętrzne.
  • Systemy powinny być powiadamiane o wszelkich zmianach danych ogólnych (takich jak pozycja lub orientacja). Dlatego podczas wykonywania zadania scena lub obiekt systemowy informuje obserwatora o wszelkich zmianach. W takim przypadku obserwator faktycznie działa jako kontroler zmian, który jest częścią menedżera stanu.
  • Kolejki kontrolera zmian zmieniają powiadomienia o dalszym przetwarzaniu. Ignoruje zmiany, które nie są istotne dla obserwatora.
  • Aby skorzystać z niektórych usług, zadanie kontaktuje się z menedżerem usług. Menedżer usług umożliwia także zmianę właściwości innych systemów, które nie są dostępne do transmisji w silniku przesyłania komunikatów (na przykład system wprowadzania danych zmienia rozszerzenie ekranu - właściwość systemu graficznego).
  • Zadania mogą również kontaktować się z menedżerem środowiska w celu uzyskania zmiennych środowiskowych i zmiany stanu wykonania (wstrzymaj wykonanie, przejdź do następnej sceny itp.).


Rysunek 12. Menedżer zadań i zadania

6.3.2 Aktualizacja danych
Po wykonaniu wszystkich zadań bieżącego cyklu zegara główna pętla gry zwraca się do menedżera stanu, aby rozpocząć fazę aktualizacji danych.
  • Menedżer stanu na przemian wywołuje każdy ze swoich kontrolerów zmian, aby wysyłać zgromadzone powiadomienia. Kontroler sprawdza, które monitory wysyłają powiadomienia o zmianie dla każdego z podmiotów.
  • Następnie dzwoni do pożądanego obserwatora i informuje go o zmianie (powiadomienie zawiera również wskaźnik do interfejsu podmiotu). W swobodnym trybie wykonywania krok po kroku obserwator otrzymuje zmienione dane od kontrolera zmian, ale w trybie wykonywania krok po kroku musi sam żądać ich od podmiotu.
  • Zazwyczaj obserwatorami zainteresowanymi otrzymywaniem powiadomień o zmianach w obiekcie systemowym są inne obiekty systemowe związane z tym samym obiektem uniwersalnym. Pozwala to podzielić proces wprowadzania zmian na kilka zadań, które można wykonywać równolegle. Aby uprościć proces synchronizacji, możesz połączyć w jednym zadaniu wszystkie powiązane rozszerzenia obiektu uniwersalnego.
6.3.3 Weryfikacja wykonania i wyjścia
Ostatnim etapem cyklu gry jest sprawdzenie stanu środowiska wykonawczego. Istnieje kilka takich stanów: praca, pauza, następna scena itp. Jeśli zostanie wybrany stan „praca”, rozpocznie się kolejna iteracja cyklu. Status wyjścia oznacza zakończenie cyklu, zwolnienie zasobów i wyjście z aplikacji. Można zrealizować inne stany, na przykład „pauza”, „następna scena” itp.

7. Wnioski

Główną ideę tego artykułu przedstawiono w rozdziale 2 „Stan równoległego wykonania”. Dzięki rozkładowi funkcjonalnemu i rozkładowi danych możliwe jest zrealizowanie nie tylko wielowątkowości silnika, ale także jego skalowalności do jeszcze większej liczby rdzeni w przyszłości. Aby wyeliminować narzut związany z synchronizacją poprzez dalsze aktualizowanie danych, oprócz mechanizmu przesyłania komunikatów używaj menedżerów stanu.

Wzorzec obserwatora jest funkcją silnika przesyłania komunikatów. Ważne jest, aby dobrze zrozumieć zasadę jego działania, aby wybrać najlepszy sposób wdrożenia go w silniku. W rzeczywistości jest to mechanizm interakcji między różnymi systemami, który zapewnia synchronizację wspólnych danych.

Ważną rolą w równoważeniu obciążenia jest zarządzanie zadaniami. Dodatek D zawiera wskazówki dotyczące tworzenia skutecznego menedżera zadań dla silnika gry.

Jak widać, wielowątkowość silnika gry można zrealizować dzięki dobrze zdefiniowanej strukturze i mechanizmowi przesyłania komunikatów. Dzięki niemu możesz znacznie poprawić wydajność nowoczesnych i przyszłych procesorów.

Dodatek A. Układ silnika

Przetwarzanie rozpoczyna się od głównego cyklu gry (patrz ryc. 4, „Główny cykl gry”).


Dodatek B. Schemat interakcji między silnikiem a układami


Dodatek C. Obserwator (wzór projektowy)

Wzorzec Observer jest szczegółowo opisany w książce Object-Oriented Design Techniques. Design Patterns ”, E. Gamma, R. Helm, R. Johnson, J. Vlissides („ Design Designs: Elements of Reusable Object-Oriented Software ”, Gamma E., Helm R., Johnson R., Vlissides J.). W języku angielskim został opublikowany po raz pierwszy w 1995 roku przez Addison-Wesley.

Główna idea tego modelu jest następująca: jeśli niektóre elementy wymagają powiadamiania o zmianach w innych elementach, nie muszą przeglądać listy wszystkich możliwych zmian, próbując znaleźć w nim niezbędne dane. Model zakłada obecność podmiotu i obserwatora, które służą do wysyłania powiadomień o zmianach. Obserwator śledzi wszelkie zmiany w temacie. Kontroler zmian działa jako pośrednik między tymi dwoma elementami. Poniższy schemat ilustruje to połączenie.


Rysunek 13. Wzorzec obserwatora

Proces korzystania z tego modelu opisano poniżej.

  1. Kontroler zmian rejestruje obserwatora i podmiot, którego powiadomienia chce otrzymywać.
  2. Kontroler zmian jest w rzeczywistości obserwatorem. Zamiast obserwatora rejestruje się wraz z podmiotem. Administrator zmian prowadzi również listę obserwatorów i zarejestrowanych u nich podmiotów.
  3. Podmiot czyni obserwatora (tj. Kontrolera zmian) na swojej liście obserwatorów, którzy chcą otrzymywać powiadomienia o swoich zmianach. Czasami dodatkowo wskazany jest rodzaj zmian, który określa, które zmiany są zainteresowane obserwatorem. Pozwala to zoptymalizować proces wysyłania powiadomień o zmianach.
  4. Zmieniając dane lub stan, podmiot powiadamia obserwatora za pośrednictwem mechanizmu zwrotnego i przesyła informacje o zmienionych typach.
  5. Kontroler zmian tworzy kolejkę powiadomień o zmianach i czeka na rozprowadzenie sygnału między obiektami i systemami.
  6. Podczas dystrybucji kontroler zmian uzyskuje dostęp do prawdziwych obserwatorów.
  7. Obserwatorzy żądają od podmiotu informacji o zmienionych danych lub stanie (lub otrzymują je wraz z powiadomieniami).
  8. Przed usunięciem obserwatora lub jeśli nie musi już otrzymywać powiadomień o temacie, usuwa subskrypcję tego tematu w kontrolerze zmian.
Istnieje wiele różnych sposobów wdrażania dystrybucji zadań. Najlepiej jednak zachować liczbę wątków roboczych równą liczbie dostępnych procesorów platformy logicznej. Staraj się nie wiązać zadań z określonym wątkiem. Czas realizacji zadań różnych systemów nie zawsze się pokrywa. Może to prowadzić do nierównomiernego rozkładu obciążenia między przepływami pracy i wpływać na wydajność. Aby uprościć ten proces, użyj na przykład bibliotek zarządzania zadaniami

Po zapoznaniu się z teorią wielowątkowości, rozważ praktyczny przykład - Pentium 4. Już na etapie opracowywania tego procesora inżynierowie Intela kontynuowali prace nad zwiększeniem prędkości bez wprowadzania zmian w interfejsie oprogramowania. Rozważono pięć prostych metod:

Zwiększenie prędkości zegara;

Umieszczenie dwóch procesorów na jednym układzie;

Wprowadzenie nowych bloków funkcjonalnych;

Przedłużenie przenośnika;

Zastosowanie wielowątkowości.

Najbardziej oczywistym sposobem na poprawę wydajności jest zwiększenie częstotliwości taktowania bez zmiany innych parametrów. Z reguły każdy kolejny model procesora ma nieco wyższą prędkość zegara niż poprzedni. Niestety, przy prostym wzroście częstotliwości taktowania, programiści napotykają dwa problemy: wzrost zużycia energii (co jest ważne w przypadku laptopów i innych urządzeń komputerowych zasilanych bateriami) oraz przegrzanie (co wymaga stworzenia bardziej wydajnych radiatorów).

Druga metoda - umieszczenie dwóch procesorów na chipie - jest stosunkowo prosta, ale wymaga podwojenia powierzchni zajmowanej przez chip. Jeśli każdy procesor jest wyposażony we własną pamięć podręczną, liczba układów na płycie jest zmniejszona o połowę, ale oznacza to również podwojenie kosztów produkcji. Jeśli dla obu procesorów zapewniona jest wspólna pamięć podręczna, można uniknąć znacznego wzrostu zajmowanego obszaru, jednak w tym przypadku pojawia się inny problem - ilość pamięci podręcznej w odniesieniu do każdego procesora jest zmniejszona o połowę, co nieuchronnie wpływa na wydajność. Ponadto, podczas gdy profesjonalne aplikacje serwerowe są w stanie w pełni wykorzystać zasoby kilku procesorów, w zwykłych programach komputerowych wewnętrzna równoległość jest rozwijana w znacznie mniejszym stopniu.

Wprowadzenie nowych bloków funkcjonalnych również nie jest trudne, ale ważne jest utrzymanie równowagi. Jaki jest sens dziesięciu bloków ALU, jeśli mikroukład nie może wydawać poleceń przenośnikowi z taką prędkością, która pozwala załadować wszystkie te bloki?

Przenośnik ze zwiększoną liczbą kroków, zdolny do dzielenia zadań na mniejsze segmenty i przetwarzania ich przez krótki czas, z jednej strony, poprawia wydajność, z drugiej strony, wzmacnia negatywne konsekwencje niewłaściwego przewidywania przejść, braków w pamięci podręcznej, przerw i innych zdarzeń, które zakłócają normalny przebieg przetwarzanie poleceń w procesorze. Ponadto, aby w pełni wykorzystać możliwości rozszerzonego przenośnika, konieczne jest zwiększenie częstotliwości taktowania, a to, jak wiemy, prowadzi do zwiększonego zużycia energii i wymiany ciepła.

Wreszcie można wdrożyć wielowątkowość. Zaletą tej technologii jest wprowadzenie dodatkowego strumienia oprogramowania, który pozwala uruchomić zasoby sprzętowe, które w innym przypadku byłyby bezczynne. Zgodnie z wynikami badań eksperymentalnych, programiści Intela stwierdzili, że wzrost powierzchni układu o 5% dzięki wdrożeniu wielowątkowości dla wielu aplikacji daje wzrost wydajności o 25%. Pierwszym procesorem Intel z obsługą wielowątkowości był Heon 2002. Następnie, począwszy od częstotliwości 3,06 GHz, wprowadzono wielowątkowość w linii Pentium 4. Intel nazywa implementację wielowątkowości w hiperwątkowości Pentium 4.

  • Instruktaż

W tym artykule postaram się opisać terminologię stosowaną do opisu systemów, które mogą wykonywać kilka programów równolegle, to znaczy wielordzeniowych, wieloprocesorowych i wielowątkowych. Różne typy współbieżności w procesorze IA-32 pojawiały się w różnym czasie i w nieco niespójnej kolejności. W tym wszystkim łatwo się pomylić, szczególnie biorąc pod uwagę, że systemy operacyjne starannie ukrywają szczegóły przed niezbyt wyszukanymi aplikacjami.

Celem tego artykułu jest pokazanie, że przy całej różnorodności możliwych konfiguracji systemów wieloprocesorowych, wielordzeniowych i wielowątkowych dla działających na nich programów, tworzone są możliwości zarówno dla abstrakcji (ignorowanie różnic), jak i dla specyficzności (możliwość programowego uczenia się konfiguracji).

Sign Warning ®, ™, w artykule

Wyjaśniam, dlaczego pracownicy firmy powinni używać znaków autorskich w komunikacji publicznej. W tym artykule trzeba było ich używać dość często.

procesor

Oczywiście najstarszym, najczęściej używanym i dwuznacznym terminem jest „procesor”.

We współczesnym świecie procesor to coś (paczka), które kupujemy w pięknym pudełku detalicznym lub niezbyt pięknej torbie OEM. Niepodzielny byt włożony do gniazda na płycie głównej. Nawet jeśli nie ma złącza i nie można go usunąć, to znaczy, jeśli jest mocno lutowane, jest to jeden układ.

Systemy mobilne (telefony, tablety, laptopy) i większość komputerów stacjonarnych mają jeden procesor. Stacje robocze i serwery mogą czasami pochwalić się co najmniej dwoma procesorami na tej samej płycie głównej.

Obsługa wielu procesorów w jednym systemie wymaga wielu zmian w jego konstrukcji. Konieczne jest co najmniej zapewnienie ich fizycznego połączenia (zapewnienie kilku gniazd na płycie głównej), rozwiązanie problemów związanych z identyfikacją procesora (patrz dalej w tym artykule, a także mojej notatce), koordynacja dostępu do pamięci i dostarczanie przerwań (kontroler przerwań musi być w stanie kierować przerwania do kilka procesorów) i oczywiście wsparcie z systemu operacyjnego. Niestety nie znalazłem wzmianki o dokumentacji dotyczącej stworzenia pierwszego systemu wieloprocesorowego na procesorach Intela, ale Wikipedia twierdzi, że Sequent Computer Systems dostarczył je już w 1987 roku przy użyciu procesorów Intel 80386. Dostępna jest powszechna obsługa kilku układów w jednym systemie począwszy od Intel® Pentium.

Jeśli jest kilka procesorów, każdy z nich ma własne złącze na płycie. Jednocześnie każdy z nich ma kompletne niezależne kopie wszystkich zasobów, takich jak rejestry, urządzenia wykonawcze, pamięci podręczne. Dzielą wspólną pamięć - RAM. Pamięć może łączyć się z nimi na różne i raczej nietrywialne sposoby, ale jest to osobna historia, która wykracza poza zakres tego artykułu. Ważne jest, aby w każdej sytuacji dla programów wykonywalnych stworzyć iluzję jednolitej pamięci współużytkowanej, dostępnej ze wszystkich procesorów wchodzących w skład systemu.


Gotowy do startu! Płyta główna Intel® D5400XS do komputerów stacjonarnych

Rdzeń

Historycznie wielordzeniowy procesor Intel IA-32 pojawiał się później w technologii Intel® HyperThreading, ale w logicznej hierarchii idzie on dalej.

Wydaje się, że jeśli system ma więcej procesorów, to jego wydajność jest wyższa (w zadaniach, które mogą korzystać ze wszystkich zasobów). Jeśli jednak koszt komunikacji między nimi jest zbyt wysoki, wszystkie korzyści z współbieżności są zabijane przez duże opóźnienia w przesyłaniu wspólnych danych. Dokładnie to obserwuje się w systemach wieloprocesorowych - zarówno fizycznie, jak i logicznie, są bardzo daleko od siebie. Aby zapewnić skuteczną komunikację w tych warunkach, musisz opracować specjalne magistrale, takie jak Intel® QuickPath Interconnect. Zużycie energii, rozmiar i cena ostatecznego rozwiązania oczywiście nie spadają z tego wszystkiego. Na ratunek powinna przyjść wysoka integracja komponentów - obwody wykonujące części programu równoległego powinny być przyciągnięte bliżej siebie, najlepiej jeden chip. Innymi słowy, w jednym procesorze kilka rdzenie, we wszystkim identycznym, ale działającym niezależnie.

Pierwsze wielordzeniowe procesory Intel IA-32 zostały wprowadzone w 2005 roku. Od tego czasu średnia liczba rdzeni w serwerach, komputerach stacjonarnych, a teraz platformach mobilnych stale rośnie.

W przeciwieństwie do dwóch jednordzeniowych procesorów w jednym systemie, które współużytkują tylko pamięć, dwa rdzenie mogą również mieć wspólne pamięci podręczne i inne zasoby odpowiedzialne za interakcję z pamięcią. Najczęściej pamięci podręczne pierwszego poziomu pozostają prywatne (każdy rdzeń ma swój własny), natomiast poziomy drugi i trzeci mogą być zarówno ogólne, jak i osobne. Taka organizacja systemu pozwala zmniejszyć opóźnienia w dostarczaniu danych między sąsiednimi rdzeniami, zwłaszcza jeśli pracują one nad wspólnym zadaniem.


Mikrografia czterordzeniowego procesora Intel o kryptonimie Nehalem. Przydzielone są oddzielne rdzenie, ogólna pamięć podręczna trzeciego poziomu, a także łącza QPI do innych procesorów i wspólny kontroler pamięci.

Hyperthread

Do około 2002 r. Jedynym sposobem na uzyskanie systemu IA-32 zdolnego do jednoczesnego wykonywania dwóch lub więcej programów było użycie systemów wieloprocesorowych. W Intel® Pentium® 4, a także w składzie Xeon, o nazwie kodowej Foster (Netburst), wprowadzono nową technologię - hiperwątki lub hiperwątki - Intel® HyperThreading (zwany dalej HT).

Nie ma nic nowego pod słońcem. HT jest szczególnym przypadkiem tego, co w literaturze określa się jako równoczesną wielowątkowość (SMT). W przeciwieństwie do „prawdziwych” jąder, które są pełnymi i niezależnymi kopiami, w przypadku HT tylko jedna część wewnętrznych węzłów jest duplikowana w jednym procesorze, odpowiedzialnym przede wszystkim za przechowywanie rejestrów stanu architektury. Węzły wykonawcze odpowiedzialne za organizowanie i przetwarzanie danych pozostają osobliwe i co najwyżej jeden z wątków używa jednego ze strumieni. Podobnie jak rdzenie, hyperthreads współużytkują pamięć podręczną, ale zaczynając od tego, jaki poziom zależy od konkretnego systemu.

Nie będę próbował wyjaśniać wszystkich zalet i wad projektów za pomocą SMT w ogóle, a w szczególności HT. Zainteresowany czytelnik może znaleźć dość szczegółową dyskusję na temat technologii w wielu źródłach i oczywiście na Wikipedii. Zwracam jednak uwagę na następującą ważną kwestię wyjaśniającą obecne ograniczenia liczby hiperwątków w rzeczywistych produktach.

Limity wątków
W jakich przypadkach uzasadniona jest obecność „nieuczciwego” rdzenia w postaci HT? Jeśli jeden wątek aplikacji nie jest w stanie załadować wszystkich wykonujących się węzłów wewnątrz jądra, można je „pożyczyć” do innego wątku. Jest to typowe dla aplikacji, które mają wąskie gardło nie w obliczeniach, ale podczas uzyskiwania dostępu do danych, to znaczy często generują brak pamięci podręcznej i muszą czekać na dostarczenie danych z pamięci. W tej chwili jądro bez HT będzie zmuszone stać bezczynnie. Obecność HT pozwala na szybkie przełączenie wolnych węzłów wykonawczych do innego stanu architektonicznego (ponieważ jest on po prostu zduplikowany) i wykonanie instrukcji. Jest to szczególny przypadek techniki zwanej ukrywaniem opóźnień, gdy jedna długa operacja, podczas której przydatne zasoby są bezczynne, jest maskowana przez równoległe wykonywanie innych zadań. Jeśli aplikacja ma już wysoki stopień wykorzystania zasobów jądra, obecność hiperwątków nie pozwoli na przyspieszenie - tutaj potrzebujemy „uczciwych” jąder.

Typowe aplikacje desktopowe i serwerowe zaprojektowane dla architektur maszyn ogólnego przeznaczenia mają potencjał równoległości realizowanej za pomocą HT. Jednak potencjał ten jest szybko „konsumowany”. Być może z tego powodu na prawie wszystkich procesorach IA-32 liczba sprzętowych hiperwątków nie przekracza dwóch. W typowych scenariuszach zysk z zastosowania trzech lub więcej hiperwątków byłby niewielki, ale utrata wielkości kryształu, jego zużycia energii i kosztu jest znacząca.

Inną sytuację obserwuje się w typowych zadaniach wykonywanych na akceleratorach wideo. Dlatego architektury te charakteryzują się zastosowaniem technologii SMT z większą liczbą wątków. Ponieważ koprocesory Intel® Xeon Phi (wprowadzone w 2010 r.) Są ideologicznie i genealogicznie dość zbliżone do kart graficznych, mogą one cztery hyperflow na każdym rdzeniu jest unikalną konfiguracją dla IA-32.

Procesor logiczny

Spośród trzech opisanych „poziomów” równoległości (procesory, rdzenie, hiperwątki) niektóre lub wszystkie z nich mogą być nieobecne w określonym systemie. Wpływ na to mają ustawienia BIOS-u (wielordzeniowy i wielowątkowy są wyłączane niezależnie), funkcje mikroarchitektury (na przykład HT był nieobecny w Intel® Core ™ Duo, ale został zwrócony wraz z wydaniem Nehalem) i zdarzenia systemowe (serwery wieloprocesorowe mogą wyłączyć uszkodzone procesory w przypadku awarii) i dalej „latać” na pozostałych). W jaki sposób to wielopoziomowe zoo współbieżności jest widoczne dla systemu operacyjnego i ostatecznie dla aplikacji?

Ponadto dla wygody oznaczamy liczbę procesorów, rdzeni i wątków w niektórych systemach o trzy ( x, y, z), gdzie x to liczba procesorów y - liczba rdzeni w każdym procesorze, oraz z - liczba hiperwątków w każdym rdzeniu. Następnie nazywam to trzy topologia - ustalony termin, który ma niewiele wspólnego z sekcją matematyki. Kompozycja p = xyz określa liczbę jednostek, o których mowa procesory logiczne system. Określa całkowitą liczbę niezależnych kontekstów procesów aplikacji w systemie z pamięcią współdzieloną, działającym równolegle, który system operacyjny musi wziąć pod uwagę. Mówię „wymuszony”, ponieważ nie może kontrolować kolejności wykonywania dwóch procesów znajdujących się na różnych procesorach logicznych. Dotyczy to również hiperwątków: chociaż działają one „sekwencyjnie” na jednym rdzeniu, określona kolejność jest podyktowana sprzętem i nie jest dostępna do monitorowania ani kontrolowania programów.

Najczęściej system operacyjny ukrywa przed końcowymi aplikacjami funkcje fizycznej topologii systemu, na którym działa. Na przykład następujące trzy topologie: (2, 1, 1), (1, 2, 1) i (1, 1, 2) - system operacyjny będzie prezentował się w postaci dwóch logicznych procesorów, chociaż pierwszy z nich ma dwa procesory, drugi - dwa jądra, a trzeci - tylko dwie nici.


Menedżer zadań Windows zawiera 8 logicznych procesorów; ale ile kosztuje procesorów, rdzeni i hipertekstów?


Top Linux zawiera 4 logiczne procesory.

Jest to dość wygodne dla twórców aplikacji - nie muszą oni zajmować się funkcjami sprzętowymi, które często są dla nich nieistotne.

Definicja topologii oprogramowania

Oczywiście abstrakcja topologii na jedną liczbę procesorów logicznych w niektórych przypadkach stwarza wystarczającą podstawę do zamieszania i nieporozumień (w gorących sporach internetowych). Aplikacje komputerowe, które chcą wycisnąć maksymalną wydajność z żelaza, wymagają szczegółowej kontroli nad tym, gdzie będą umieszczane ich wątki: bliżej siebie na sąsiednich hiperwątkach lub odwrotnie, dalej na różnych procesorach. Szybkość komunikacji między procesorami logicznymi w ramach jednego rdzenia lub procesora jest znacznie wyższa niż prędkość przesyłania danych między procesorami. Możliwość heterogeniczności w organizacji pamięci o swobodnym dostępie również komplikuje obraz.

Informacje o topologii systemu jako całości, a także o pozycji każdego procesora logicznego w IA-32, są dostępne przy użyciu instrukcji CPUID. Od czasu pojawienia się pierwszych systemów wieloprocesorowych schemat identyfikacji procesorów logicznych kilkakrotnie się rozszerzył. Do tej pory jego części są zawarte w arkuszach 1, 4 i 11 CPUID. Który z arkuszy należy obejrzeć można określić na podstawie następującej schematu blokowego zaczerpniętego z artykułu:

Nie będę się tu nudził wszystkimi szczegółami poszczególnych części tego algorytmu. Jeśli pojawią się odsetki, można poświęcić temu następną część tego artykułu. Wyślę zainteresowanego czytelnika do, w którym pytanie to jest rozumiane w jak największym stopniu. Tutaj najpierw krótko opisuję, czym jest APIC i jak odnosi się do topologii. Następnie rozważymy pracę z arkuszem 0xB (jedenaście po przecinku), który w tej chwili jest ostatnim słowem w „inżynierii wierzchołkowej”.

ID APIC
Lokalny APIC (zaawansowany programowalny kontroler przerwań) jest urządzeniem (obecnie stanowiącym część procesora) odpowiedzialnym za pracę z przerwaniami przychodzącymi do określonego procesora logicznego. Każdy procesor logiczny ma swój własny APIC. I każdy z nich w systemie musi mieć unikalną wartość APIC ID. Liczba ta jest używana przez kontrolery przerwań do adresowania, gdy dostarczane są wiadomości, oraz przez wszystkie inne (na przykład system operacyjny) do identyfikacji procesorów logicznych. Specyfikacja tego kontrolera przerwań ewoluowała od Intel 8259 PIC poprzez Dual PIC, APIC i xAPIC do x2APIC.

Obecnie szerokość liczby przechowywanej w identyfikatorze APIC osiągnęła pełne 32 bity, chociaż w przeszłości była ograniczona do 16, a nawet wcześniej - tylko 8 bitów. Dziś resztki dawnych czasów są rozrzucone po CPUID, jednak wszystkie 32 bity identyfikatora APIC są zwracane w CPUID.0xB.EDX. Na każdym procesorze logicznym, który niezależnie wykonuje instrukcję CPUID, zwracana jest jej wartość.

Wyjaśnienie relacji
Sama wartość identyfikatora APIC nie mówi nic o topologii. Aby dowiedzieć się, które dwa logiczne procesory znajdują się w tym samym fizycznym (tzn. Są „braćmi” hipertekstów), które dwa są w tym samym procesorze i które są zupełnie różnymi procesorami, musisz porównać ich wartości identyfikatora APIC. W zależności od stopnia powinowactwa niektóre ich bity będą pasować. Informacje te są zawarte w podlistie CPUID.0xB, która jest zakodowana przy użyciu argumentu w ECX. Każdy z nich opisuje pozycję pola bitowego jednego z poziomów topologii w EAX (a dokładniej liczbę bitów, które należy przesunąć w prawo w identyfikatorze APIC, aby usunąć niższe poziomy topologii), a także typ tego poziomu - hiperwątek, rdzeń lub procesor - w ECX.

Procesory logiczne w tym samym rdzeniu będą miały te same bity identyfikatora APIC, z wyjątkiem tych należących do pola SMT. W przypadku procesorów logicznych znajdujących się w jednym procesorze wszystkie bity oprócz pól Core i SMT. Ponieważ liczba pod-arkuszy dla CPUID.0xB może wzrosnąć, ten schemat umożliwi obsługę opisu topologii z większą liczbą poziomów, jeśli to konieczne w przyszłości. Ponadto możliwe będzie wprowadzenie poziomów pośrednich między istniejącymi.

Ważną konsekwencją organizacji tego schematu jest to, że mogą występować „dziury” w zestawie wszystkich identyfikatorów APIC wszystkich logicznych procesorów w systemie, tj. nie pójdą sekwencyjnie. Na przykład w procesorze wielordzeniowym z wyłączonym HT wszystkie identyfikatory APIC mogą okazać się parzyste, ponieważ najmniej znaczący bit odpowiedzialny za kodowanie liczby hiperwątków zawsze będzie wynosił zero.

Zwracam uwagę, że CPUID.0xB nie jest jedynym źródłem informacji o procesorach logicznych dostępnych dla systemu operacyjnego. Lista wszystkich dostępnych dla niej procesorów wraz z ich wartościami APIC ID jest zakodowana w tabeli MADT ACPI.

Systemy operacyjne i topologia

Systemy operacyjne dostarczają aplikacjom logiczne informacje o topologii procesorów przy użyciu własnych interfejsów.

W systemie Linux informacje o topologii znajdują się w pseudopliku / proc / cpuinfo, a także w wynikach komendy dmidecode. W poniższym przykładzie filtruję zawartość cpuinfo w niektórych systemach czterordzeniowych bez HT, pozostawiając tylko wpisy topologiczne:

Ukryty tekst

[chroniony e-mailem]: ~ $ cat / proc / cpuinfo | grep "procesor \\ | physical \\ id \\ | rodzeństwo \\ | rdzeń \\ | rdzenie \\ | apicid" procesor: 0 identyfikator fizyczny: 0 rodzeństwo: 4 identyfikator rdzenia: 0 rdzeni procesora: 2 apicid: 0 początkowy apicid: 0 procesor: 1 identyfikator fizyczny: 0 rodzeństwo: 4 identyfikator rdzenia: 0 rdzeni procesora: 2 apicid: 1 początkowy apicid: 1 procesor: 2 identyfikator fizyczny: 0 rodzeństwo: 4 identyfikator rdzenia: 1 rdzeni procesora: 2 apicid: 2 początkowy apicid: 2 procesor: 3 identyfikator fizyczny: 0 rodzeństwo: 4 identyfikator rdzenia: 1 rdzenie procesora: 2 apicid: 3 początkowy apicid: 3

W FreeBSD topologia jest raportowana przez mechanizm sysctl w zmiennej kern.sched.topology_spec jako XML:

Ukryty tekst

[chroniony e-mailem]: ~ $ sysctl kern.sched.topology_spec kern.sched.topology_spec: 0, 1, 2, 3, 4, 5, 6, 7 0, 1, 2, 3, 4, 5, 6, 7 0, 1 Grupa GWINTGrupa SMT 2, 3 Grupa GWINTGrupa SMT 4, 5 Grupa GWINTGrupa SMT 6, 7 Grupa GWINTGrupa SMT

W MS Windows 8 informacje o topologii można zobaczyć w Menedżerze zadań.

W branży informacyjnej początek XXI wieku zbiegł się ze zmianami, które można określić jako „tektoniczne”. Znaki nowej ery obejmują wykorzystanie architektur zorientowanych na usługi (architektura zorientowana na usługi, SOA), konfiguracji klastrów i wiele, wiele więcej, w tym procesorów wielordzeniowych. Ale oczywiście podstawową przyczyną tego, co się dzieje, jest rozwój fizyki półprzewodników, co spowodowało wzrost liczby elementów logicznych na jednostkę powierzchni, zgodnie z prawem Gordona Moore'a. Liczba tranzystorów na chipie jest już w setkach milionów i wkrótce pokona miliardowy kamień milowy, w wyniku czego działanie znanego prawa dialektycznego nieuchronnie przejawia się, postulując związek zmian ilościowych i jakościowych. W zmienionych warunkach na pierwszy plan wysuwa się nowa kategoria - złożoność, a systemy stają się złożone zarówno na poziomie mikro (procesory), jak i na poziomie makro (korporacyjne systemy informacyjne).

Do pewnego stopnia to, co dzieje się we współczesnym świecie komputerowym, można porównać do przejścia ewolucyjnego, które nastąpiło miliony lat temu, kiedy pojawiły się organizmy wielokomórkowe. W tym czasie złożoność jednej komórki osiągnęła pewien limit, a późniejsza ewolucja poszła ścieżką rozwoju złożoności infrastrukturalnej. To samo dzieje się z systemami komputerowymi: złożoność jednego rdzenia procesora, a także monolityczna architektura korporacyjnych systemów informatycznych osiągnęły określone maksimum. Teraz na poziomie makro trwa przejście od systemów monolitycznych do komponentów (lub usług), a uwaga deweloperów koncentruje się na oprogramowaniu infrastruktury oprogramowania pośredniego, a nowe architektury procesorów pojawiają się na poziomie mikro.

Dosłownie w ostatnim czasie idea złożoności zaczęła tracić swoje powszechnie używane znaczenie, przekształcając się w niezależny czynnik. Pod tym względem złożoność nie jest jeszcze w pełni zrozumiana, a stosunek do niej nie jest dobrze zdefiniowany, chociaż, o dziwo, przez prawie 50 lat istniała osobna dyscyplina naukowa, zwana „teorią systemów złożonych”. (Przypomnijmy, że teoretycznie system „złożony” nazywa się systemem, którego poszczególne komponenty są łączone w sposób nieliniowy; taki system to nie tylko suma komponentów, jak to ma miejsce w systemach liniowych.) Można się tylko dziwić, że teoria systemów nie została jeszcze zaakceptowana przez tych specjalistów i firmy. których działania prowadzą ich do tworzenia złożonych systemów za pomocą technologii informatycznych.

„Wąskie gardło” architektury von Neumanna

Na poziomie mikro analogiem przejścia z organizmów jednokomórkowych do wielokomórkowych może okazać się przejście z jedno-rdzeniowych procesorów na wielordzeniowe (Chip MultiProcessors, CMP). CMP zapewnia jedną z możliwości przezwyciężenia nieodłącznej słabości współczesnych procesorów - „wąskie gardło” architektury von Neumanna.

Oto, co powiedział John Backus podczas ceremonii wręczenia nagrody Turinga w 1977 r .: „Co to jest komputer von Neumanna? Kiedy 30 lat temu John von Neumann i inni zaproponowali swoją oryginalną architekturę, pomysł wydawał się elegancki, praktyczny i umożliwił uproszczenie rozwiązania wielu problemów inżynierskich i programistycznych. Chociaż warunki, które istniały w czasie jego publikacji, zmieniły się radykalnie z czasem, utożsamiamy nasze pomysły na temat komputerów z tą starą koncepcją. W najprostszej prezentacji komputer von Neumann składa się z trzech części: jest to procesor centralny (CPU lub CPU), pamięć i kanał łączący je, który służy do wymiany danych między CPU a pamięcią oraz w małych porcjach (tylko jedno słowo). Proponuję nazywać ten kanał „wąskim gardłem von Neumanna”. Z pewnością powinno być mniej prymitywne rozwiązanie niż przepompowywanie ogromnej ilości danych przez „wąskie wąskie gardło”. Taki kanał nie tylko stwarza problem dla ruchu, ale jest także „wąskim gardłem intelektualnym”, które narzuca programistom myślenie „słowo po słowie”, uniemożliwiając im rozumowanie w wyższych kategoriach koncepcyjnych ”.

Najbardziej znanym Backusem było stworzenie języka Fortran w połowie lat 50. XX wieku, który przez kilka następnych dziesięcioleci był najpopularniejszym sposobem tworzenia programów osadniczych. Ale później najwyraźniej Backus głęboko uświadomił sobie swoje słabości i zdał sobie sprawę, że opracował „najbardziej język von Neumanna” ze wszystkich języków wysokiego poziomu. Dlatego główny patos jego krytyki dotyczył przede wszystkim niedoskonałych metod programowania.

Od czasu, gdy Backus wygłosił mowę programistyczną, nastąpiły znaczące zmiany, pojawiły się technologie funkcjonalne i obiektowe, a przy ich pomocy udało im się pokonać to, co Backus nazwał „wąskim gardłem intelektualisty von Neumanna”. Jednak podstawa architektoniczna tego zjawiska, wrodzona choroba kanału między pamięcią a procesorem - jego ograniczona przepustowość - nie zniknęła, pomimo postępu technologicznego w ciągu ostatnich 30 lat. Z biegiem lat problem ten stale się pogłębia, ponieważ szybkość pamięci rośnie znacznie wolniej niż wydajność procesorów, a różnica między nimi rośnie.

Architektura komputerów Von Neumann nie jest jedyną możliwą. Z punktu widzenia organizacji wymiany poleceń między procesorem a pamięcią wszystkie komputery można podzielić na cztery klasy:

  • SISD (Single Instruction Single Data) - „jeden strumień poleceń, jeden strumień danych” ”;
  • SIMD (Pojedyncza instrukcja mnożenia danych) - jeden strumień poleceń, wiele strumieni danych;
  • MISD (Multiple Instruction Single Data) - wiele strumieni poleceń, jeden strumień danych;
  • MIMD (wiele instrukcji wiele danych) - wiele strumieni poleceń, wiele strumieni danych.

Ta klasyfikacja pokazuje, że maszyna von Neumanna jest specjalnym przypadkiem, który należy do kategorii SISD. Możliwe ulepszenia w architekturze SISD są ograniczone do włączenia potoków i innych dodatkowych węzłów funkcjonalnych, a także zastosowania różnych metod buforowania. Dwie inne kategorie architektur (SIMD, która obejmuje procesory wektorowe i architektury MISD działające w trybie potokowym) zostały wdrożone w kilku projektach, ale nie stały się powszechne. Jeśli pozostaniemy w ramach tej klasyfikacji, jedynym sposobem na przezwyciężenie ograniczeń wąskiego gardła jest rozwój architektury klasy MIMD. W ich ramach odkrywa się wiele podejść: mogą to być różne architektury równoległe i klastrowe oraz procesory wielowątkowe.

Jeszcze kilka lat temu, ze względu na ograniczenia technologiczne, wszystkie procesory wielowątkowe zostały zbudowane w oparciu o jeden rdzeń, a taki wielowątkowy został nazwany „jednoczesny” - Jednoczesny wielowątkowość (SMT). Wraz z pojawieniem się procesorów wielordzeniowych pojawił się alternatywny rodzaj wielowątkowości - Chip MultiProcessors (CMP).

Funkcje procesorów wielowątkowych

Przejście od prostych procesorów jednowątkowych do logicznie bardziej złożonych procesorów wielowątkowych wiąże się z pokonywaniem specyficznych trudności, których wcześniej nie napotkano. Funkcjonowanie urządzenia, w którym proces wykonywania jest podzielony na agenty lub wątki, wyróżnia się dwiema cechami:

  • zasada nieokreśloności. W aplikacji wielowątkowej proces jest podzielony na przepływy agentów oddziałujące ze sobą bez określonej pewności;
  • zasada nieoznaczoności. Dokładne rozdzielenie zasobów między przepływy agentów również nie jest znane z góry.

Ze względu na te cechy działanie procesorów wielowątkowych zasadniczo różni się od obliczeń deterministycznych zgodnie ze schematem von Neumanna. W tym przypadku bieżącego stanu procesu nie można zdefiniować jako funkcji liniowej poprzedniego stanu i danych otrzymanych na wejściu, chociaż każdy z procesów można uznać za mikromaszynę von Neumanna. (Jako dodatek do zachowania przepływów można nawet użyć terminu „dziwność” stosowanego w fizyce kwantowej.) Obecność tych cech przybliża wielowątkowy procesor do idei złożonego układu, ale z czysto praktycznego punktu widzenia jest jasne, że na poziomie wykonania procesu nie ma determinizmu ani niepewność, a nawet więcej na temat obcości i mowy, nie może być. Prawidłowo wykonany program nie może być dziwny.

W najbardziej ogólnej formie wielowątkowy procesor składa się z dwóch rodzajów operacji podstawowych. Pierwszy typ to zasób, który obsługuje wykonywanie wątku o nazwie mutex (z wzajemnego wykluczenia - „wzajemne wykluczenie”), a drugi - zdarzenia. Sposób fizycznego wdrożenia tego lub innego muteksu zależy od wybranego schematu - SMT lub CMP. W każdym razie wykonanie procesu sprowadza się do tego, że następny wątek przechwytuje muteks na czas jego wykonywania, a następnie uwalnia go. Jeśli mutex jest zajęty jednym wątkiem, drugi wątek nie może go uzyskać. Konkretna procedura przekazywania uprawnień do posiadania muteksu z jednego wątku do drugiego może być losowa; zależy to od wdrożenia zarządzania, na przykład w konkretnym systemie operacyjnym. W każdym razie należy zbudować kontrolę, aby zasoby składające się z muteksu były przydzielane poprawnie, a efekt niepewności został stłumiony.

Zdarzenia to obiekty sygnalizujące zmianę w środowisku zewnętrznym. Mogą przejść w tryb gotowości przed innym wydarzeniem lub zgłosić swój status do innego wydarzenia. W ten sposób zdarzenia mogą ze sobą współdziałać, a jednocześnie należy zapewnić ciągłość danych między zdarzeniami. Oczekujący agent musi zostać poinformowany o dostępności danych dla niego. I podobnie jak w rozkładzie muteksu, efekt niepewności musi być tłumiony, tak więc podczas pracy ze zdarzeniami efekt niepewności musi być tłumiony. Po raz pierwszy obwód SMT został zaimplementowany w procesorach Compaq Alpha 21464, a także w procesorach Intel Xeon MP i Itanium.

Logicznie rzecz biorąc, CMP jest prostszy: tutaj współbieżność jest zapewniona przez fakt, że każdy wątek jest przetwarzany przez własne jądro. Ale jeśli aplikacji nie można podzielić na wątki, wówczas (przy braku specjalnych środków) jest ona przetwarzana przez jeden rdzeń, aw tym przypadku ogólna wydajność procesora jest ograniczona przez prędkość jednego rdzenia. Na pierwszy rzut oka procesor oparty na schemacie SMT jest bardziej elastyczny i dlatego ten schemat jest preferowany. Ale takie stwierdzenie jest prawdziwe tylko w przypadku niskiej gęstości tranzystorów. Jeśli częstotliwość jest mierzona w megahercach, a liczba tranzystorów w krysztale jest bliska miliarda, a opóźnienie w transmisji sygnału staje się dłuższe niż czas przełączania, wówczas mikroarchitektura CMP, w której zlokalizowane są połączone elementy obliczeniowe, przynosi korzyści.

Fizyczna równoległość prowadzi jednak do tego, że CMP nie jest bardzo skuteczny w obliczeniach sekwencyjnych. Aby przezwyciężyć tę wadę, stosuje się podejście zwane „wielowątkowością spekulacyjną”. W języku rosyjskim słowo „spekulatywne” ma negatywną konotację semantyczną, dlatego taką wielowątkowość nazwiemy „warunkową”. Podejście to obejmuje obsługę sprzętu lub oprogramowania do dzielenia aplikacji sekwencyjnej na wątki warunkowe, koordynowania ich wykonywania i integracji wyników w pamięci.

Ewolucja CMP

Pierwsze masowe procesory CMP były przeznaczone na rynek serwerów. Niezależnie od producenta były to w istocie dwa niezależne superskalarne procesory na tym samym podłożu. Główną motywacją do tworzenia takich struktur jest zmniejszenie objętości, aby więcej procesorów można było „spakować” w jedną konstrukcję, zwiększając moc jednostkową na wolumin (co jest niezwykle ważne w nowoczesnych centrach danych). Następnie, na poziomie ogólnym systemu, osiągnięto pewne dodatkowe oszczędności, ponieważ procesory znajdujące się na tym samym układzie wykorzystują wspólne zasoby systemowe, takie jak szybka komunikacja. Zwykle między sąsiednimi procesorami istnieje tylko wspólny interfejs systemu ( figa. 1b).

Apologeci za wykorzystanie procesorów CMP potwierdzają dalszy (ponad dwa) wzrost liczby rdzeni dzięki cechom obciążenia serwera, które odróżniają ten typ komputera od wbudowanego lub przeznaczonego do masowych systemów obliczeniowych. Serwer wymaga dużej ogólnej wydajności, ale opóźnienie w jednym wywołaniu do systemu nie jest tak ważne. Trywialny przykład: użytkownik może po prostu nie zauważyć milisekundowego opóźnienia w pojawieniu się zaktualizowanej strony internetowej, ale bardzo boleśnie reaguje na przeciążenie serwera, co może powodować przerwy w świadczeniu usług.

Specyfika obciążenia daje procesorom CMP kolejną istotną zaletę. Na przykład, zastępując procesor jednordzeniowy dwurdzeniowym, przy tej samej wydajności, szybkość zegara można zmniejszyć o połowę. Jednocześnie teoretycznie czas przetwarzania dla pojedynczego żądania może się podwoić, ale ponieważ fizyczny rozdział przepływów zmniejsza ograniczenie „wąskiego gardła” architektury von Neumanna, całkowite opóźnienie będzie znacznie mniejsze niż dwa razy. Przy mniejszej częstotliwości i złożoności jednego rdzenia zużycie energii jest znacznie zmniejszone, a wraz ze wzrostem liczby rdzeni powyższe argumenty na korzyść CMP tylko rosną. Dlatego kolejnym logicznym krokiem jest zebranie kilku rdzeni i połączenie ich ze wspólną pamięcią podręczną, na przykład jak w projekcie Hydra (ryc. 1, c). Następnie można skomplikować rdzenie i uczynić je wielowątkowymi, co zostało wdrożone w projekcie Niagara (ryc. 1, d).

Złożoność procesorów ma jeszcze jeden ważny przejaw. Projektowanie produktu z miliardami komponentów staje się coraz bardziej czasochłonnym zadaniem - pomimo zastosowania automatyzacji. Znaczące jest to, że jesteśmy świadkami ponad dekady „przypominania” architektury IA-64. Projektowanie procesora CMP jest znacznie prostsze: jeśli istnieje dobrze rozwinięty rdzeń, można go replikować we właściwych ilościach, a projektowanie ogranicza się do stworzenia wewnętrznej infrastruktury krystalicznej. Ponadto jednorodność rdzeni upraszcza projektowanie płyt głównych, co sprowadza się do skalowania, a ostatecznie zmienia się wydajność podsystemów we / wy.

Pomimo powyższych argumentów nie ma wystarczających podstaw do jednoznacznego stwierdzenia o zaletach CMP w porównaniu z SMT. Doświadczenie w tworzeniu procesorów wdrażających SMT jest znacznie większe: od połowy lat 80. powstało kilkadziesiąt eksperymentalnych produktów i kilka procesorów szeregowych. Historia CPM jest wciąż krótka: jeśli nie weźmie się pod uwagę rodziny wyspecjalizowanych procesorów sygnałowych Texas Instruments TMS 320C8x, to pierwszym udanym projektem była Hydra, wykonana na Uniwersytecie Stanforda. Wśród uniwersyteckich projektów badawczych mających na celu budowę procesorów CMP znane są jeszcze trzy - Wisconsin Multiscalar, Carnegie-Mellon Stampede i MIT M.

Mikroprocesor Hydra

Kryształ Hydry składa się z czterech rdzeni procesora opartych na dobrze znanej architekturze MIPS RISC. Każdy rdzeń ma pamięć podręczną poleceń i pamięć podręczną danych, a wszystkie rdzenie są łączone we wspólną pamięć podręczną drugiego poziomu. Procesory wykonują zwykły zestaw instrukcji MIPS plus instrukcje warunkowego przechowywania (Store Conditional lub SC) zaprojektowane do implementacji operacji podstawowych synchronizacji. Procesory i pamięć podręczna na drugim poziomie są połączone przez szyny odczytu / zapisu, a ponadto istnieją dodatkowe szyny adresowe i sterujące. Wszystkie te magistrale są wirtualne, czyli logicznie reprezentowane przez magistrale drutowe, i są fizycznie podzielone na wiele segmentów za pomocą repeaterów i buforów, co pozwala zwiększyć prędkość rdzeni.

Magistrala odczytu / zapisu pełni rolę magistrali systemowej. Ze względu na położenie wewnątrz układu, ma wystarczającą przepustowość, aby zapewnić wymianę z pamięcią podręczną w jednym cyklu. Trudno jest osiągnąć takie wskaźniki wydajności wymiany nawet w najdroższych tradycyjnych architekturach wieloprocesorowych ze względu na fizyczne ograniczenia liczby zewnętrznych styków procesorów. Wydajne magistrale pamięci podręcznej zapobiegają wąskim gardłom między rdzeniami a pamięcią.

Testowanie Hydry pod obciążeniem z wyraźną równoległością w typowych aplikacjach sieciowych i serwerowych wykazało, że wydajność czterech rdzeni w porównaniu z jednym rdzeniem wzrasta o 3-3,8 razy, czyli prawie liniowo. Daje to powód, by sądzić, że procesory tego typu dość dobrze „pasują” do aplikacji, które korzystają z serwerów o architekturze SMP. Oczywiste jest jednak, że procesor powinien działać dość wydajnie z aplikacjami szeregowymi, więc jednym z najważniejszych zadań jest wdrożenie warunkowego wielowątkowości. W Hydrze jest on wdrażany na poziomie sprzętowym, a wybór tego podejścia jest uzasadniony faktem, że nie wymaga on dodatkowych kosztów programowania równoległych aplikacji.

Warunkowe wielowątkowość polega na podzieleniu sekwencji instrukcji programu na wątki, które można wykonywać równolegle. Naturalnie może istnieć logiczna współzależność między takimi przepływami, a do ich koordynacji w procesorze wbudowany jest specjalny mechanizm synchronizacji. Istotą jego pracy jest to, że jeśli jakiś wątek potrzebuje danych z równoległego strumienia, ale nie są jeszcze gotowe, wykonanie takiego strumienia jest zawieszone. W rzeczywistości elementy niedeterminizmu, o których mowa powyżej, manifestują się tutaj. Proces synchronizacji jest raczej skomplikowany, ponieważ konieczne jest określenie wszystkich możliwych zależności między przepływami a warunkami synchronizacji. Synchronizacja warunkowa umożliwia zrównoleglenie programów bez uprzedniej znajomości ich właściwości. Ważne jest, aby mechanizm synchronizacji był dynamiczny; działa bez interwencji programisty lub kompilatora, który jest w stanie jedynie statycznie dzielić aplikacje na wątki. Testy modelu oparte na różnych testach wykazały, że warunkowe narzędzia wielowątkowe mogą kilkakrotnie zwiększyć wydajność procesora, a im bardziej wyraźna jest równoległość charakteryzowana przez test, tym niższa jest ta wartość.

W 2000 r. Afara została utworzona w środowisku o ścisłej tajemnicy. Jego założycielami byli profesor Kunle Olukotun z Uniwersytetu Stanforda i słynny twórca procesorów Les Cohn, który miał doświadczenie w Intel i Sun Microsystems. Cohn był jednym z autorów procesorów RISC i860 i i960 w pierwszej z tych korporacji, a UltraSPARC-I w drugiej. Pod jego kierownictwem Hydra została przeprojektowana dla rdzeni procesorów opartych na procesorze SPARC. W 2002 r. Afara została kupiona przez Sun Microsystems, co zakończyło historię projektu Hydra i rozpoczęła się historia Niagary.

Niagara - „stop” MAJC i Hydry

Procesor UltraSPARC T1, lepiej znany jako Niagara, ma dwa główne poprzedniki - Hydrę i MAJC.

W połowie lat 90., po entuzjazmie dla wyspecjalizowanych procesorów Java, Sun Microsystems próbował stworzyć procesor z „bardzo długim słowem” - bardzo długim słowem instrukcji (VLIW). Ta inicjatywa nosi nazwę MAJC (architektura mikroprocesorów dla Java Computing). Podobnie jak w innych projektach, które rozpoczęły się w tym czasie (Intel IA-64 Itanium), w tym przypadku zadaniem było przeniesienie niektórych najbardziej skomplikowanych operacji do kompilatora. Uwolnioną logikę tranzystorową można wykorzystać do stworzenia bardziej produktywnych jednostek funkcjonalnych, aby zapewnić wydajną wymianę poleceń i danych między procesorem, pamięcią podręczną i pamięcią główną. W ten sposób udało się pokonać wąskie gardło von Neumanna.

MAJC różniło się od większości procesorów brakiem wyspecjalizowanych podprocesorów, które zwykle nazywane są urządzeniami funkcjonalnymi zaprojektowanymi do wykonywania operacji na liczbach całkowitych, liczbach zmiennoprzecinkowych i danych multimedialnych. W nim wszystkie urządzenia funkcjonalne były takie same, zdolne do wykonywania dowolnych operacji, co z jednej strony zmniejszało wydajność poszczególnych operacji, ale z drugiej strony zwiększało wykorzystanie całego procesora.

Niagara ucieleśnia najlepsze z dwóch alternatywnych podejść wielowątkowych - SMT i CMP. Na pierwszy rzut oka jest bardzo podobny do Hydry, ale raczej Hydrę można nazwać „układem” Niagara. Poza tym, że ten drugi ma dwa razy więcej rdzeni, każdy z nich może przetwarzać cztery wątki.

Procesor Niagara zapewnia sprzętową obsługę 32 wątków, które są podzielone na osiem grup (po cztery wątki). Każda grupa ma własny potok kanału przetwarzania SPARC ( rys. 2) Jest to rdzeń procesora zbudowany zgodnie z architekturą SPARC V9. Każdy potok SPARC zawiera pamięć podręczną pierwszego poziomu dla instrukcji i danych. Łącznie 32 wątki używają 3 MB pamięci podręcznej L2 podzielonej na cztery banki. Przełącznik łączy osiem rdzeni, banki pamięci podręcznej drugiego poziomu i inne rozproszone zasoby procesora oraz obsługuje szybkość transferu 200 GB / s. Ponadto przełącznik ma port dla systemów wejścia / wyjścia oraz kanały pamięci DDR2 DRAM, które zapewniają szybkość wymiany wynoszącą 20 GB / s; Maksymalna pojemność pamięci wynosi do 128 GB.

Projekt Niagara koncentruje się na systemie operacyjnym Solaris, więc wszystkie aplikacje działające w systemie Solaris mogą działać na nowym procesorze bez żadnych zmian. Oprogramowanie postrzega Niagarę jako 32 dyskretne procesory.

Projekt komórki

Autorskie podejście do tworzenia procesorów wielordzeniowych zaproponowała firma IBM, której projekt Cell nazwano „heterogenicznym układem wieloprocesorowym” (heterogeniczny układ wieloprocesorowy). Architektura komórki nazywana jest również architekturą Cell Broadband Engine Architecture (CBEA). Multiprocesor komórkowy składa się z rdzenia 64-bitowej architektury zasilania IBM i ośmiu wyspecjalizowanych koprocesorów, które implementują schemat „jeden zespół wiele danych”. W IBM ta architektura nazywa się Synergistic Processor Unit (SPU). W razie potrzeby można go z powodzeniem wykorzystać do przetwarzania dużych strumieni danych, na przykład w kryptografii, w różnych aplikacjach multimedialnych i naukowych, takich jak szybka transformacja Fouriera lub operacje macierzowe. Architektura Cell została stworzona przez zespół badaczy IBM Research wraz z kolegami z IBM Systems Technology Group, Sony i Toshiba, a jej pierwszą aplikacją powinny być urządzenia multimedialne wymagające dużej ilości obliczeń.

Podstawą Synergistic Processor Unit jest zestaw instrukcji ISA (Instruction Set Architecture). Polecenia mają długość 32 bitów i są adresowane do trzech argumentów umieszczonych w puli rejestrów, która składa się ze 128 rejestrów po 128 bitów każdy.

W przyszłości korzystanie z Cell nie będzie ograniczone do systemów gier. Kolejne w kolejności są telewizja wysokiej rozdzielczości, serwery domowe, a nawet superkomputery.

Literatura
  1. Leonid Czerniak. Rewizja podstawowych zasad - koniec stagnacji? // Systemy otwarte. - 2003, nr 5.
  2. Michaił Kuźminski. Architektura wielowątkowa mikroprocesora // Systemy otwarte. - 2002, nr 1.
  3. Rajat A Dua, Bhushan Lokhande. Badanie porównawcze wieloprocesorów SMT i CMP. -

Po zapoznaniu się z teorią wielowątkowości, rozważ praktyczny przykład - Pentium 4. Już na etapie opracowywania tego procesora inżynierowie Intela kontynuowali prace nad zwiększeniem prędkości bez wprowadzania zmian w interfejsie oprogramowania. Rozważono pięć prostych metod:
1. Zwiększ częstotliwość taktowania.
2. Umieszczenie dwóch procesorów na jednym układzie.
3. Wprowadzenie nowych bloków funkcjonalnych.
1. Przedłużenie przenośnika.
2. Zastosowanie wielowątkowości.
Najbardziej oczywistym sposobem na poprawę wydajności jest zwiększenie częstotliwości taktowania bez zmiany innych parametrów. Z reguły każdy kolejny model procesora ma nieco wyższą prędkość zegara niż poprzedni. Niestety, przy prostym wzroście częstotliwości taktowania, programiści napotykają dwa problemy: wzrost zużycia energii (co jest ważne w przypadku laptopów i innych urządzeń komputerowych zasilanych bateriami) oraz przegrzanie (co wymaga stworzenia bardziej wydajnych radiatorów).
Druga metoda - umieszczenie dwóch procesorów na chipie - jest stosunkowo prosta, ale wymaga podwojenia powierzchni zajmowanej przez chip. Jeśli każdy procesor jest wyposażony we własną pamięć podręczną, liczba układów na płycie jest zmniejszona o połowę, ale oznacza to również podwojenie kosztów produkcji. Jeśli dla obu procesorów zapewniona jest wspólna pamięć podręczna, można uniknąć znacznego wzrostu zajmowanego obszaru, jednak w tym przypadku pojawia się inny problem - ilość pamięci podręcznej w odniesieniu do każdego procesora jest zmniejszona o połowę, co nieuchronnie wpływa na wydajność. Ponadto, podczas gdy profesjonalne aplikacje serwerowe są w stanie w pełni wykorzystać zasoby kilku procesorów, w zwykłych programach komputerowych wewnętrzna równoległość jest rozwijana w znacznie mniejszym stopniu.
Wprowadzenie nowych bloków funkcjonalnych również nie jest trudne, ale ważne jest utrzymanie równowagi. Jaki jest sens dziesięciu bloków ALU, jeśli mikroukład nie może wydawać poleceń przenośnikowi z taką prędkością, która pozwala załadować wszystkie te bloki?
Przenośnik ze zwiększoną liczbą kroków, zdolny do dzielenia zadań na mniejsze segmenty i przetwarzania ich przez krótki czas, z jednej strony, poprawia wydajność, z drugiej strony, wzmacnia negatywne konsekwencje niewłaściwego przewidywania przejść, braków w pamięci podręcznej, przerw i innych zdarzeń, które zakłócają normalny przebieg przetwarzanie poleceń w procesorze. Ponadto, aby w pełni wykorzystać możliwości rozszerzonego przenośnika, konieczne jest zwiększenie częstotliwości taktowania, a to, jak wiemy, prowadzi do zwiększonego zużycia energii i wymiany ciepła.
Wreszcie można wdrożyć wielowątkowość. Zaletą tej technologii jest wprowadzenie dodatkowego strumienia oprogramowania, który pozwala uruchomić zasoby sprzętowe, które w innym przypadku byłyby bezczynne. Zgodnie z wynikami badań eksperymentalnych, programiści Intela stwierdzili, że wzrost powierzchni układu o 5% dzięki wdrożeniu wielowątkowości dla wielu aplikacji daje wzrost wydajności o 25%. Pierwszym wielowątkowym procesorem Intela był Xeon 2002. Następnie, począwszy od częstotliwości 3,06 GHz, wprowadzono wielowątkowość w linii Pentium 4. Intel nazywa implementację wielowątkowości w hiperwątkowości Pentium 4.
Podstawową zasadą hiperwątkowania jest jednoczesne wykonywanie dwóch wątków programu (lub procesów - procesor nie odróżnia procesów od wątków programu). System operacyjny uważa procesor hiperwątkowy Pentium 4 za kompleks dwuprocesorowy ze współużytkowanymi buforami i pamięcią główną. System operacyjny wykonuje planowanie dla każdego strumienia programu osobno. Dlatego dwie aplikacje mogą być uruchomione jednocześnie. Na przykład program pocztowy może wysyłać lub odbierać wiadomości w tle, podczas gdy użytkownik wchodzi w interakcję z aplikacją interaktywną - to znaczy, że demon i program użytkownika działają jednocześnie, tak jakby w systemie były dostępne dwa procesory.
Programy aplikacyjne, które zapewniają możliwość wykonania w postaci kilku strumieni programów, mogą korzystać z obu „procesorów wirtualnych”. Na przykład programy do edycji wideo zazwyczaj pozwalają użytkownikom stosować filtry do wszystkich klatek. Takie filtry dostosowują jasność, kontrast, balans kolorów i inne właściwości ramek. W takiej sytuacji program może wyznaczyć jeden procesor wirtualny do przetwarzania ramek parzystych, a drugi do przetwarzania ramek nieparzystych. W takim przypadku dwa procesory będą działać całkowicie niezależnie od siebie.
Ponieważ wątki oprogramowania mają dostęp do tych samych zasobów sprzętowych, konieczna jest koordynacja tych wątków. W kontekście hiperwątkowości programiści Intel zidentyfikowali cztery przydatne strategie zarządzania zużyciem zasobów współdzielonych: powielanie zasobów, a także twarde, progowe i pełne udostępnianie zasobów. Rozważ te strategie.
Zacznijmy od duplikacji zasobów. Jak wiadomo, niektóre zasoby są duplikowane w celu organizowania przepływów programów. Na przykład, ponieważ każdy strumień programu wymaga indywidualnego sterowania, potrzebny jest drugi licznik instrukcji. Ponadto konieczne jest wprowadzenie drugiej tabeli do mapowania rejestrów architektonicznych (EAX, EVX itp.) Do rejestrów fizycznych; podobnie kontroler przerwań jest duplikowany, ponieważ przetwarzanie przerwań dla każdego wątku jest wykonywane osobno.
Poniżej przedstawiono technikę dzielenia zasobów na partycje między strumieniami programu. Na przykład, jeśli procesor zapewnia kolejkę między dwoma etapami funkcjonalnymi potoku, wtedy połowa szczelin może zostać przekazana do strumienia 1, a druga połowa do strumienia 2. Rozdzielenie zasobów można łatwo wdrożyć, nie prowadzi do nierównowagi i zapewnia całkowitą niezależność przepływów programu od siebie. Po całkowitym oddzieleniu wszystkich zasobów jeden procesor faktycznie zamienia się w dwa. Z drugiej strony może wystąpić sytuacja, w której jeden wątek programu nie wykorzystuje zasobów, które mogą być przydatne dla drugiego wątku, ale do których nie ma uprawnień dostępu. W rezultacie zasoby, które mogłyby być zaangażowane, są bezczynne.
Przeciwieństwem twardego udostępniania jest pełne udostępnianie zasobów. W tym schemacie dowolny strumień programu może uzyskać dostęp do niezbędnych zasobów i są one obsługiwane w kolejności odbierania żądań dostępu. Rozważmy sytuację, w której szybki strumień, składający się głównie z operacji dodawania i odejmowania, współistnieje z wolnym strumieniem, który realizuje operacje mnożenia i dzielenia. Jeśli instrukcje są wywoływane z pamięci szybciej niż operacje mnożenia i dzielenia, liczba instrukcji wywoływanych jako część wolnego strumienia i ustawianych w kolejce w potoku będzie stopniowo rosła. Ostatecznie te polecenia wypełnią kolejkę, w wyniku czego szybki przepływ zostanie zatrzymany z powodu braku miejsca. Całkowite rozdzielenie zasobów rozwiązuje problem nieoptymalnego wydatkowania wspólnych zasobów, ale powoduje nierównowagę w ich zużyciu - jeden przepływ może spowolnić lub zatrzymać inny.
Schemat pośredni jest wdrażany jako część progowego współdzielenia zasobów. Zgodnie z tym schematem każdy strumień programu może dynamicznie odbierać określoną (ograniczoną) ilość zasobów. W przypadku replikowanych zasobów podejście to zapewnia elastyczność bez zagrożenia przestojem jednego z przepływów programu z powodu niemożności uzyskania zasobów. Jeśli na przykład zabronisz zajmowania przez każdy wątek więcej niż 3/4 kolejki poleceń, zwiększone zużycie zasobów przez wolny wątek nie zakłóci szybkiego wykonywania.
Model Hyper-Threading Pentium 4 łączy wiele strategii udostępniania zasobów. W związku z tym podejmuje się próbę rozwiązania wszystkich problemów związanych z każdą strategią. Duplikacja jest realizowana w odniesieniu do zasobów, do których dostęp jest stale wymagany przez oba przepływy programu (w szczególności w odniesieniu do licznika instrukcji, tabeli mapowania rejestru i kontrolera przerwań). Duplikacja tych zasobów zwiększa obszar mikroukładu tylko o 5% - widzicie, całkiem rozsądna opłata za wielowątkowość. Zasoby dostępne w takim wolumenie, że prawdopodobieństwo ich przechwycenia przez jeden wątek (na przykład linie pamięci podręcznej) jest praktycznie wykluczone, są rozkładane dynamicznie. Dostęp do zasobów sterujących działaniem potoku (w szczególności jego wielu kolejek) jest współdzielony - połowa przedziałów przydzielana jest do każdego strumienia programu. Główny potok architektury Netburst zaimplementowanej w Pentium 4 pokazano na ryc. 8,7; białe i szare obszary na tej ilustracji wskazują mechanizm alokacji zasobów między białymi i szarymi wątkami programu.
Jak widać, wszystkie kolejki na tej ilustracji są podzielone - do każdego strumienia programu jest przydzielona połowa miejsc. Żaden z wątków programu nie może ograniczać pracy innego. Blok dystrybucji i podstawiania jest również podzielony. Zasoby programu planującego są udostępniane dynamicznie, ale w oparciu o określoną wartość progową - dlatego żaden z wątków nie może zajmować wszystkich miejsc w kolejce. Dla wszystkich innych etapów przenośnika istnieje całkowite rozdzielenie.
Jednak wielowątkowość nie jest taka prosta. Nawet taka postępowa technika ma wady. Trudna alokacja zasobów nie wiąże się z poważnymi kosztami, ale dynamiczna separacja, szczególnie biorąc pod uwagę wartości progowe, wymaga monitorowania zużycia zasobów na etapie wykonania. Ponadto w niektórych przypadkach programy działają znacznie lepiej bez wielowątkowości niż przy nim. Załóżmy na przykład, że jeśli istnieją dwa wątki programu do normalnej pracy, każdy z nich wymaga 3/4 pamięci podręcznej. Gdyby były wykonywane naprzemiennie, każdy z nich wykazywałby wystarczającą wydajność przy niewielkiej ilości braków pamięci podręcznej (jak wiadomo, wiąże się to z dodatkowymi kosztami). W przypadku równoległego wykonywania braków pamięci podręcznej wszyscy mieliby znacznie więcej, a końcowy wynik byłby gorszy niż bez wielowątkowości.
Dodatkowe informacje na temat mechanizmu wielowątkowości RepPit 4 można znaleźć w.

DZWONEK

Są tacy, którzy czytają te wiadomości przed tobą.
Subskrybuj, aby otrzymywać świeże artykuły.
E-mail
Imię
Nazwisko
Jak chcesz przeczytać Dzwon
Bez spamu