To nie pomyłka – oba procesory ustawione na taką samą prędkość taktowania, z identycznie taktowaną pamięcią RAM i takimi samymi opóźnieniami mają w niektórych zastosowaniach radykalnie różną wydajność. W eksportowaniu złożonego projektu do dwóch różnych formatów jednocześnie model 3300X był o 30% szybszy, ale nawet po przyspieszeniu obu procesorów zachowuje przewagę ponad 20%! W innych zastosowaniach różnice są dużo mniejsze, ale niektóre gry działają zauważalnie lepiej na procesorze ze wszystkimi rdzeniami w jednym CCX.
System operacyjny wie o różnej budowie tych dwóch procesorów. Co prawda pamięci podręczne są zarządzane sprzętowo (oprogramowanie „widzi” tylko rejestry procesora i jednolitą przestrzeń pamięci, a procesor sam decyduje, która część tej pamięci będzie przechowywana chwilowo w szybkiej, lokalnej pamięci podręcznej), ale żeby ułatwić systemowi operacyjnemu przydzielanie wątków, procesor może przedstawić mu topologię rdzeni i pamięci podręcznych. Oprogramowanie może zatem wiedzieć (bez pewności, że to prawda), jaka jest pojemność i organizacja cache, ale nie może bezpośrednio wpłynąć na to, co jest w niej przechowywane. Na pierwszym zrzucie ekranu widzicie dostępne dla systemu operacyjnego informacje o topologii Ryzena 3300X (jeden CCX), a na drugim – Ryzena 3100 (dwa CCX).
Systemy operacyjne (sprawdziłem Windows 10 i Ubuntu 20.04) biorą te informacje pod uwagę – dwuwątkowy program zostanie przydzielony na Ryzenie 3 3100 do tych procesorów logicznych, które działają w ramach jednego CCX.
Nie do końca wspólna pamięć podręczna L3
Najpoważniejszą konsekwencją takiej budowy procesora jest to, że dla procesu lub grupy procesów działających w ramach jednego CCX druga część pamięci podręcznej L3 często nie ma znaczenia. Jeden wątek może dostać do dyspozycji najwyżej 8 MB danych z prędkością L3 – po większe porcje procesor musi sięgnąć do RAM-u.
Zwróćcie uwagę na skalę logarytmiczną na obu osiach.
Komunikacja między wątkami czy pojemność pamięci podręcznej?
Co sprawia, że Ryzen 3 3300X jest o tyle wydajniejszy od modelu 3100 – czy to, że komunikacja między niektórymi parami rdzeni w 3100 jest wolniejsza, czy to, że jego pamięć podręczna nie jest wspólna dla wszystkich rdzeni?
Komunikacja między procesorami polega na tym, że działające na nich programy korzystają z tych samych obszarów pamięci. Wyobraźmy sobie dwa rdzenie jako dwóch ludzi symulujących maszyny Turinga, stojących w jednym pomieszczeniu. Każdy z nich trzyma w ręku inny fragment jednej taśmy maszyny Turinga. Nie mogą się skomunikować w żaden inny sposób, niż za pośrednictwem taśmy – w procesorze nie ma kanału komunikacji odpowiadającego rozmowie lub gestykulacji, która nie korzysta z taśmy.
Tych dwóch czynników nie da się odizolować; pamięć podręczna uczestniczy w komunikacji między rdzeniami tak samo jak łącze InfinityFabric.
Numerację wątków zmieniłem na tę odpowiadająca systemom Windows. Procesory logiczne 0 i 1 odpowiadają temu samemu rdzeniowi i mają do siebie najbliżej. 3 i 4 działają na drugim rdzeniu i mają wspólną z 0 i 1 pamięć L3. W Ryzenie 3100 komunikacja wątków 0-3 z wątkami 4-7 trwa dłużej, bo te drugie znajdują się w drugim CCX. Trzeba sięgnąć do jądra CCIO za pośrednictwem łącza InfinityFabric – nie ma bezpośredniej komunikacji pomiędzy dwoma CCX w ramach jednego jądra CCD.
Połączona pula pamięci podręcznej L3 jest w większości przypadków lepsza – zadania małowątkowe mogą mieć w zasięgu szybkiego dostępu całe 16 MB danych. Jednak mogą się zdarzyć osobliwe zadania, w których dwa osobne segmenty pamięci podręcznej L3 dają lepszą wydajność, niż jeden o podwójnej pojemności. Użyteczne dane mogą zostać wyrzucone z pamięci podręcznej z różnych powodów:
-
kiedy inny proces działający na rdzeniu połączonym z ta samą pulą cache potrzebuje innych danych – dwa procesy rywalizują o pojemność cache
-
kiedy świeżo pobierane dane nie wyczerpują pojemności cache, ale mogą być przechowywane tylko w okreśolnym miejscu, bo drożność cache jest za mała – czyli dwa procesy rywalizują o konkretne linie w cache
W ten sposób zbyt dużo czasu zostaje zmarnowane na wielokrotne pobieranie i wyrzucanie danych z cache, i żaden proces nie ma potrzebnych sobie danych dość długo, żeby najlepiej wykorzystać prędkość i bliskość pamięci cache. W programowaniu nazywa się to zjawisko cache thrashing.
Spróbowałem znaleźć jakiś przykład takiego zachowania przeprowadzając mały eksperyment z Blenderem. W procesorze Zen 2 z włączonymi 4 rdzeniami w różnych konfiguracjach pozwoliłem Blenderowi korzystać tylko z 2 wątków przypisanych przez system operacyjny do dowolnych spośród 4 procesorów logicznych. Niebieskie zaznaczenie pokazuje, które zasoby miał do dyspozycji Blender. Windows oczywiście bierze pod uwagę technikę SMT – dwa wątki mające do dyspozycji 4 procesory logiczne zostają przypisane tak, żeby na jednym rdzeniu działał tylko jeden wątek.
Jak można było się spodziewać, najszybsza jest konfiguracja D, w której dwa wątki Blendera mają do dyspozycji po 16 MB L3 każdy. Spośród konfiguracji B i C, które mają do dyspozycji łącznie 16 MB L3, szybsza jest ta, w której jest to wspólne 16 MB. Porównanie A z C oraz B z D pokazuje, że pojemność dostępnej pamięci jest równie ważna, co jej dostępność dla obu procesów. Oczywiście to tylko jeden przykład – na pewno w samym Blenderze znalazłyby się jakieś procedury, które pokazałyby inne zależności w wydajności. Sądzę jednak, że ten przykład wystarczy, żeby pokazać, że dyskusja o ważności jednego czy drugiego z dwóch nierozłącznie związanych czynników nie ma sensu.