Jak działa pojedynczy wątek na wielu rdzeniach?

61

Próbuję zrozumieć na wysokim poziomie, w jaki sposób pojedyncze wątki przebiegają przez wiele rdzeni. Poniżej znajduje się moje najlepsze zrozumienie. Nie sądzę jednak, aby było to poprawne.

Na podstawie mojego czytania hiperwątkowości wydaje się, że system operacyjny porządkuje instrukcje wszystkich wątków w taki sposób, że nie czekają na siebie. Następnie interfejs CPU dalej porządkuje te instrukcje, dystrybuując jeden wątek do każdego rdzenia i dystrybuując niezależne instrukcje z każdego wątku wśród dowolnych otwartych cykli.

Więc jeśli istnieje tylko jeden wątek, system operacyjny nie dokona żadnej optymalizacji. Jednak interfejs CPU rozdzieli niezależne zestawy instrukcji pomiędzy każdy rdzeń.

Według https://stackoverflow.com/a/15936270 określony język programowania może tworzyć więcej lub mniej wątków, ale nie ma znaczenia przy określaniu, co zrobić z tymi wątkami. Obsługują to system operacyjny i procesor, więc dzieje się tak niezależnie od używanego języka programowania.

wprowadź opis zdjęcia tutaj

Aby wyjaśnić, pytam o jeden wątek prowadzony przez wiele rdzeni, a nie o uruchamianie wielu wątków na jednym rdzeniu.

Co jest nie tak z moim podsumowaniem? Gdzie i jak instrukcje wątku dzielą się na wiele rdzeni? Czy język programowania ma znaczenie? Wiem, że to szeroki temat; Mam nadzieję na zrozumienie tego na wysokim poziomie.

Evorlor
źródło
6
Zestaw instrukcji dla pojedynczego wątku oprogramowania może działać na wielu rdzeniach, ale nie jednocześnie.
Kroltan
1
Miksujesz wątki oprogramowania (które wymagają harmonogramu systemu operacyjnego) i wątki sprzętowe lub HyperThreading (funkcja procesora, która sprawia, że ​​jeden rdzeń zachowuje się jak dwa).
ugoren
2
Mam 20 kierowców i 4 ciężarówki. Jak to możliwe, że jeden kierowca może dostarczyć paczki z dwiema ciężarówkami? Jak to możliwe, że jedna ciężarówka może mieć wielu kierowców? Odpowiedź na oba pytania jest taka sama. Zmieniać się.
Eric Lippert,

Odpowiedzi:

84

System operacyjny oferuje wycinki czasu procesora dla wątków, które można uruchomić.

Jeśli jest tylko jeden rdzeń, wówczas system operacyjny planuje najbardziej odpowiedni wątek do uruchomienia na tym rdzeniu dla przedziału czasu. Po zakończeniu segmentu czasowego lub gdy działający wątek blokuje się na IO lub gdy procesor jest przerywany przez zdarzenia zewnętrzne, system operacyjny ponownie ocenia, który wątek ma zostać uruchomiony (i może ponownie wybrać ten sam wątek lub inny).

Kwalifikowalność do uruchomienia składa się z wariantów dotyczących uczciwości, priorytetu i gotowości, a dzięki tej metodzie różne wątki uzyskują przedziały czasowe, niektóre bardziej niż inne.

Jeśli istnieje wiele rdzeni, N, system operacyjny planuje najbardziej odpowiednie N wątków do uruchomienia na rdzeniach.

Powinowactwo procesora to kwestia wydajności. Za każdym razem, gdy procesor uruchamia inny wątek niż poprzednio, ma tendencję do nieco spowalniania, ponieważ pamięć podręczna jest ciepła dla poprzedniego wątku, ale zimna dla nowego. Zatem uruchamianie tego samego wątku na tym samym procesorze w wielu przedziałach czasowych jest zaletą wydajności.

Jednak system operacyjny może oferować jeden wątek w różnych przedziałach czasowych i może obracać się przez wszystkie procesory w różnych przedziałach czasowych. Nie może jednak, jak mówi @ gnasher729 , uruchamiać jeden wątek na wielu procesorach jednocześnie.

Hyperthreading to metoda sprzętowa, dzięki której jeden ulepszony rdzeń procesora może obsługiwać wykonywanie dwóch lub więcej różnych wątków jednocześnie. (Taki procesor może oferować dodatkowe wątki przy niższych kosztach w nieruchomościach krzemowych niż dodatkowe pełne rdzenie.) Ten ulepszony rdzeń procesora musi obsługiwać dodatkowy stan dla innych wątków, takich jak wartości rejestru procesora, a także ma stan i zachowanie koordynacyjne, które umożliwia współdzielenie jednostek funkcjonalnych w tym CPU bez łączenia wątków.

Hyperthreading, choć technicznie trudny z punktu widzenia sprzętowego, z perspektywy programisty, model wykonania jest jedynie modelem dodatkowych rdzeni procesora, a nie czymś bardziej złożonym. Tak więc system operacyjny widzi dodatkowe rdzenie procesora, choć pojawiają się pewne nowe problemy z koligacją procesora, ponieważ kilka wątków hiperwątkowanych współdzieli architekturę pamięci podręcznej jednego rdzenia procesora.


Możemy naiwnie myśleć, że dwa wątki działające na hiperszytowanym rdzeniu działają o połowę szybciej niż każdy z własnym pełnym rdzeniem. Ale niekoniecznie tak jest, ponieważ wykonanie pojedynczego wątku jest pełne wolnych cykli, a pewna ich część może być wykorzystana przez inny wątek hiperwątkowy. Ponadto, nawet w cyklach bez luzu, jeden wątek może wykorzystywać inne jednostki funkcjonalne niż drugi, więc może wystąpić jednoczesne wykonanie. Ulepszony procesor do hiperwątkowania może mieć kilka innych mocno używanych jednostek funkcjonalnych specjalnie do obsługi tego.

Erik Eidt
źródło
3
„Zatem uruchomienie tego samego wątku na tym samym procesorze w wielu przedziałach czasowych jest zaletą pod względem wydajności”. Czy nie musiałyby to być ciągłe odcinki czasu? W przeciwnym razie skrytki zostałyby wyczyszczone przez inne wątki, prawda? +1 za dobre wyjaśnienie.
jpmc26
2
@Luaan: HT jest często dobra, ale sytuacja nie jest tak prosta, jak to opisujesz. Przepustowość problemu frontonu (4 uops na zegar na Intel, 6 na Ryzen) jest równo dzielona między wątkami (chyba że jeden utknął). Jeśli to jest wąskie gardło, to tak jak powiedziałem HT w ogóle nie pomoże. Często zdarza się, że Skylake zbliża się do tego w dobrze dostrojonej pętli, jeśli istnieje mieszanka obciążeń, ALU i sklepów ... Tranzystory są tanie (i nie wszystkie mogą się przełączać jednocześnie, a procesor się stopi), więc współczesne procesory x86 mają więcej portów wykonawczych niż front-end może zasilać (wiele jednostek wykonawczych jest replikowanych ...
Peter Cordes
2
... na wielu portach) ... To może wydawać się marnotrawstwem, ale często pętla będzie używać tylko jednego rodzaju jednostki wykonawczej ALU naraz, więc posiadanie duplikatów wszystkiego oznacza, że ​​niezależnie od rodzaju kodu działa, istnieje wiele porty dla instrukcji. Dlatego powód, dla którego zacytowałeś korzystanie z HT, nie jest tak powszechny, ponieważ większość kodu ma pewne obciążenia i / lub sklepy zajmujące pasmo frontonu, a to, co pozostało, często nie wystarcza do nasycenia jednostek wykonawczych.
Peter Cordes
2
@Luaan: Ponadto w procesorach Intel liczby całkowite i jednostki wykonawcze FP / wektor mają te same porty wykonania . Na przykład jednostki FP FMA / mul / add znajdują się na portach 0/1. Ale mnożnik liczb całkowitych znajduje się również na porcie 1, a proste operacje na liczbach całkowitych mogą działać na dowolnym z 4 portów wykonania (schemat w mojej odpowiedzi). Drugi wątek zwiększający przepustowość sprawi ich spowolnienie, nawet jeśli nie konkurują one o jednostki wykonawcze, ale często występuje wzrost przepustowości netto, jeśli nie rywalizują zbyt mocno o pamięć podręczną. Nawet dobrze dostrojony, wysokoprzepustowy kod, taki jak x264 / x265 (kodery wideo), zyskuje około 15% na Skylake od HT.
Peter Cordes
3
@luaan Oprócz tego, co powiedział Peter, twoje twierdzenie, że „To było pierwotne uzasadnienie HT” jest nieprawidłowe. Pierwotnym uzasadnieniem HT było to, że mikroarchitektura NetBurst wydłużyła rurociąg tak bardzo (w celu przyspieszenia zegara), że nieprzewidywalne rozgałęzienia i inne bąbelki rurociągu całkowicie zabiły wydajność. HT było jednym z rozwiązań Intela w celu zminimalizowania czasu, przez który jednostki wykonujące ten duży i drogi procesor pozostawały bezczynne z powodu pęcherzyków w rurociągu: w tych otworach można było wstawić kod z innych wątków.
Cody Gray
24

Nie ma czegoś takiego jak pojedynczy wątek działający na wielu rdzeniach jednocześnie.

Nie oznacza to jednak, że instrukcje z jednego wątku nie mogą być wykonywane równolegle. Istnieją mechanizmy zwane potokowaniem instrukcji i wykonywaniem poza kolejnością, które na to pozwalają. Każdy rdzeń ma wiele zbędnych zasobów, które nie są wykorzystywane przez proste instrukcje, więc wiele takich instrukcji można uruchomić razem (o ile następny nie zależy od poprzedniego wyniku). Jednak nadal dzieje się to w jednym rdzeniu.

Hiperwątkowość jest rodzajem ekstremalnego wariantu tego pomysłu, w którym jeden rdzeń nie tylko wykonuje instrukcje z jednego wątku równolegle, ale miesza instrukcje z dwóch różnych wątków, aby jeszcze bardziej zoptymalizować wykorzystanie zasobów.

Powiązane wpisy w Wikipedii: Potokowanie instrukcji , wykonywanie poza kolejnością .

Frax
źródło
3
Nie mogą działać jednocześnie, ale czy mogą działać równolegle? Czy to nie to samo?
Evorlor
10
@Evorlor Kluczową kwestią jest tutaj różnica między rdzeniem a jednostką wykonawczą. Pojedynczy wątek może działać tylko na jednym rdzeniu, ale procesor może użyć analizy dynamicznej, aby ustalić, które instrukcje wykonywane przez rdzeń nie zależą od siebie i wykonać je jednocześnie na różnych jednostkach wykonawczych. Jeden rdzeń może mieć kilka jednostek wykonawczych.
user1937198
3
@Evorlor: Poza kolejnością procesor może znaleźć i wykorzystać równoległość na poziomie instrukcji w strumieniu instrukcji jednego wątku. np. często instrukcje aktualizujące licznik pętli są niezależne od niektórych innych czynności wykonywanych przez pętlę. Lub w a[i] = b[i] + c[i]pętli, każda iteracja jest niezależna, więc ładowanie, dodawanie i przechowywanie z różnych iteracji może być jednocześnie w locie. Musi zachować iluzję, że instrukcje wykonywane w kolejności programu, ale na przykład sklep, który nie trafia do pamięci podręcznej, nie opóźnia wątku (dopóki nie zabraknie miejsca w buforze sklepu).
Peter Cordes
3
@ user1937198: Wyrażenie „analiza dynamiczna” lepiej pasowałoby do kompilatora JIT. Procesory poza kolejnością tak naprawdę nie analizują; przypomina bardziej chciwy algorytm, który uruchamia wszystkie instrukcje, które zostały zdekodowane i wydane i mają przygotowane dane wejściowe. (Okno zmiany kolejności poza kolejnością jest ograniczone przez kilka zasobów mikroarchitektonicznych, na przykład Intel Sandybridge ma bufor ReOrder o wielkości 168 uops. Zobacz także eksperymentalny pomiar wielkości ROB ). Wszystko zaimplementowane ze sprzętowymi maszynami stanu do obsługi 4 impulsów na zegar.
Peter Cordes
3
@Luaan tak, to był ciekawy pomysł, ale kompilatory AOT wciąż nie są wystarczająco inteligentne, aby je w pełni wykorzystać. Ponadto Linus Torvalds (i inni) argumentowali, że ujawnienie, że duża część elementów wewnętrznych rurociągu jest dużym ograniczeniem dla przyszłych projektów. np. nie można tak naprawdę zwiększyć szerokości potoku bez zmiany ISA. Albo budujesz procesor, który śledzi zależności w zwykły sposób i być może wydaje równolegle dwie grupy VLIW, ale potem straciłeś zalety EPIC związane ze złożonością procesora, ale nadal masz wady (straciłeś przepustowość, gdy kompilator nie może wypełnić słowo).
Peter Cordes
22

Podsumowanie: Znajdowanie i wykorzystywanie równoległości (na poziomie instrukcji) w programie jednowątkowym odbywa się wyłącznie sprzętowo, przez rdzeń procesora, na którym działa. I tylko nad oknem kilkuset instrukcji, a nie na dużą skalę zamawiania.

Programy jednowątkowe nie czerpią korzyści z wielordzeniowych procesorów, z wyjątkiem tego, że inne rzeczy mogą działać na innych rdzeniach zamiast tracić czas na zadanie jednowątkowe.


system operacyjny porządkuje instrukcje wszystkich wątków w taki sposób, aby nie czekały na siebie.

System operacyjny NIE zagląda do strumieni instrukcji wątków. Planuje tylko wątki do rdzeni.

W rzeczywistości każdy rdzeń uruchamia funkcję harmonogramu systemu operacyjnego, gdy musi dowiedzieć się, co dalej. Planowanie jest algorytmem rozproszonym. Aby lepiej zrozumieć maszyny wielordzeniowe, pomyśl o każdym rdzeniu jako o osobnym uruchamianiu jądra. Podobnie jak program wielowątkowy, jądro jest napisane, aby jego kod na jednym rdzeniu mógł bezpiecznie oddziaływać z jego kodem na innych rdzeniach w celu aktualizacji wspólnych struktur danych (takich jak lista wątków, które są gotowe do uruchomienia.

W każdym razie system operacyjny bierze udział w pomaganiu procesom wielowątkowym w wykorzystaniu równoległości na poziomie wątków, które muszą być jawnie ujawnione poprzez ręczne napisanie programu wielowątkowego . (Lub przez kompilator z automatyczną równoległością z OpenMP lub coś takiego).

Następnie interfejs CPU dalej porządkuje te instrukcje, dystrybuując jeden wątek do każdego rdzenia i dystrybuując niezależne instrukcje z każdego wątku wśród dowolnych otwartych cykli.

Rdzeń procesora uruchamia tylko jeden strumień instrukcji, jeśli nie jest zatrzymany (śpi do następnego przerwania, np. Przerwania timera). Często jest to wątek, ale może to być również moduł obsługi przerwań jądra lub inny kod jądra, jeśli jądro postanowiło zrobić coś innego niż powrót do poprzedniego wątku po obsłudze i przerwie lub wywołaniu systemowym.

W przypadku HyperThreading lub innych konstrukcji SMT fizyczny rdzeń procesora działa jak wiele „logicznych” rdzeni. Jedyną różnicą z punktu widzenia systemu operacyjnego między procesorem czterordzeniowym z hyperthreadingiem (4c8t) a zwykłą maszyną 8-rdzeniową (8c8t) jest to, że system operacyjny obsługujący HT spróbuje zaplanować wątki w celu oddzielenia rdzeni fizycznych, aby nie „ konkurować ze sobą. System operacyjny, który nie wiedział o hiperwątkowaniu, zobaczyłby tylko 8 rdzeni (chyba że wyłączysz HT w BIOSie, wykryje tylko 4).


Termin „ front-end” odnosi się do części rdzenia procesora, która pobiera kod maszynowy, dekoduje instrukcje i wydaje je do części rdzenia poza kolejnością . Każdy rdzeń ma własny interfejs i jest częścią rdzenia jako całości. Pobierane przez niego instrukcje aktualnie uruchomione przez procesor.

W niedziałającej części rdzenia instrukcje (lub uops) są wysyłane do portów wykonawczych, gdy ich operandy wejściowe są gotowe i jest wolny port wykonawczy. Nie musi się to zdarzać w kolejności programów, więc w ten sposób procesor OOO może wykorzystać równoległość na poziomie instrukcji w jednym wątku .

Jeśli zamienisz „rdzeń” na „jednostkę wykonawczą” w swoim pomyśle, jesteś blisko poprawienia. Tak, procesor równolegle dystrybuuje niezależne instrukcje / polecenia do jednostek wykonawczych. (Ale istnieje pewna pomyłka terminologiczna, ponieważ powiedziałeś „front-end”, kiedy tak naprawdę to planista instrukcji CPU, zwany Reservation Station, wybiera instrukcje gotowe do wykonania).

Wykonanie poza kolejnością może znaleźć ILP tylko na poziomie lokalnym, tylko do kilkuset instrukcji, a nie między dwiema niezależnymi pętlami (chyba że są krótkie).


Na przykład równoważnik tego asm

int i=0,j=0;
do {
    i++;
    j++;
} while(42);

będzie działać tak szybko, jak ta sama pętla, zwiększając tylko jeden licznik na Intel Haswell. i++zależy tylko od poprzedniej wartości i, podczas gdy j++zależy tylko od poprzedniej wartości j, więc dwa łańcuchy zależności mogą działać równolegle, nie przerywając iluzji wszystkiego, co jest wykonywane w kolejności programu.

Na x86 pętla wyglądałaby mniej więcej tak:

top_of_loop:
    inc eax
    inc edx
    jmp .loop

Haswell ma 4 porty wykonywania liczb całkowitych, a wszystkie z nich mają jednostki sumujące, więc może utrzymać przepustowość do 4 incinstrukcji na zegar, jeśli wszystkie są niezależne. (Przy opóźnieniu = 1, więc potrzebujesz tylko 4 rejestrów, aby zmaksymalizować przepustowość, utrzymując 4 incinstrukcje w locie. Porównaj to z wektorowym FP MUL lub FMA: opóźnienie = 5 przepustowość = 0,5 potrzebuje 10 wektorowych akumulatorów, aby utrzymać 10 FMA w locie aby zmaksymalizować przepustowość. Każdy wektor może mieć 256b i pomieścić 8 pływaków o pojedynczej precyzji).

Przejęta gałąź jest również wąskim gardłem: pętla zawsze zajmuje co najmniej jeden cały zegar na iterację, ponieważ przepustowość przejętej gałęzi jest ograniczona do 1 na zegar. Mógłbym umieścić jeszcze jedną instrukcję w pętli bez zmniejszania wydajności, chyba że odczytuje / zapisuje eaxlub edxw takim przypadku wydłużyłby ten łańcuch zależności. Umieszczenie 2 dodatkowych instrukcji w pętli (lub jednej złożonej instrukcji wielopunktowej) stworzyłoby wąskie gardło w interfejsie, ponieważ może wydać tylko 4 impulsy na zegar do rdzenia poza kolejnością. (Zobacz to SO Q&A, aby uzyskać szczegółowe informacje na temat tego, co dzieje się w przypadku pętli, które nie są wielokrotnością 4 uops: bufor pętli i cache uop sprawiają, że rzeczy są interesujące.)


W bardziej skomplikowanych przypadkach znalezienie równoległości wymaga spojrzenia na większe okno instrukcji . (np. może istnieje sekwencja 10 instrukcji, które wszystkie zależą od siebie, a następnie kilka niezależnych).

Pojemność bufora ponownego zamówienia jest jednym z czynników ograniczających rozmiar okna poza kolejnością. W przypadku Intel Haswell jest to 192 ups. (I możesz nawet zmierzyć to eksperymentalnie , wraz z pojemnością zmiany nazwy rejestru (rozmiar pliku rejestru).) Rdzenie procesora o niskiej mocy, takie jak ARM, mają znacznie mniejsze rozmiary ROB, jeśli w ogóle wykonują się poza kolejnością.

Należy również pamiętać, że procesory muszą być przetwarzane potokowo, a także poza kolejnością. Musi więc pobierać i dekodować instrukcje na długo przed tymi, które są wykonywane, najlepiej o wystarczającej przepustowości, aby uzupełnić bufory po pominięciu jakichkolwiek cykli pobierania. Gałęzie są trudne, ponieważ nie wiemy, skąd je pobrać, jeśli nie wiemy, w którą stronę poszła gałąź. Właśnie dlatego przewidywanie gałęzi jest tak ważne. (I dlaczego współczesne procesory używają spekulatywnego wykonywania: domyślają się, w którą stronę pójdzie gałąź, i zaczną pobierać / dekodować / wykonywać strumień instrukcji. Po wykryciu błędnej prognozy przywracają do ostatniego znanego dobrego stanu i wykonują stamtąd.)

Jeśli chcesz dowiedzieć się więcej o wewnętrznych procesorach, na wiki wiki Stackoverflow x86 znajduje się kilka linków, w tym przewodnik mikroarchitera Agner Fog oraz szczegółowe opisy Davida Kantera ze schematami procesorów Intel i AMD. Z jego podsumowania mikroarchitektury Intel Haswell jest to ostatni schemat całego potoku rdzenia Haswella (nie całego układu).

To jest schemat blokowy pojedynczego rdzenia procesora . Czterordzeniowy procesor ma 4 na chipie, każdy z własną pamięcią podręczną L1 / L2 (współdzielenie pamięci podręcznej L3, kontrolerów pamięci i połączeń PCIe z urządzeniami systemowymi).

Pełny rurociąg Haswell

Wiem, że jest to niezwykle skomplikowane. Artykuł Kantera pokazuje także części tego, aby na przykład rozmawiać o interfejsie oddzielnie od jednostek wykonawczych lub pamięci podręcznych.

Peter Cordes
źródło
2
„Znalezienie i wykorzystanie paralelizmu (na poziomie instrukcji) w programie jednowątkowym odbywa się wyłącznie sprzętowo”. Należy pamiętać, że dotyczy to tylko tradycyjnych ISA, a nie VLIW, w których ILP jest określany całkowicie przez kompilator lub programator, lub we współpracy między sprzętem i oprogramowanie.
Hadi Brais,
1
@ user7813604: tak. Hyperthreading nie może zrównoleglać pojedynczego wątku. Robi to odwrotnie: uruchamia wiele wątków na jednym rdzeniu, zmniejszając wydajność na wątek, ale zwiększając ogólną przepustowość.
Peter Cordes,
1
@ user7813604: Cały sens ILP polega na ustaleniu, które instrukcje można uruchomić równolegle, zachowując przy tym złudzenie, że każda instrukcja działa w kolejności, a każda kończy się przed rozpoczęciem kolejnej. Skalarny procesor potokowy może czasami wymagać zatrzymania w przypadku zależności, jeśli opóźnienie jest większe niż 1. Ale jest to jeszcze większy problem dla superskalarnych procesorów.
Peter Cordes,
1
@ user7813604: tak, moja odpowiedź dosłownie wykorzystuje to jako przykład. Na przykład Haswell może wykonać do 4 incinstrukcji w tym samym cyklu zegara, na swoich 4 liczbach całkowitych jednostek wykonawczych ALU.
Peter Cordes,
1
@ user7813604: Tak, ILP określa, ile można wykonać równolegle. Rzeczywisty procesor będzie miał ograniczoną zdolność do wyszukiwania i wykorzystywania ILP poprzez faktyczne uruchomienie go równolegle w ramach jednego rdzenia, np. W superskalarach o szerokości do 4 w Intel. Ta odpowiedź próbuje wyjaśnić to przykładami.
Peter Cordes,