Dlaczego silniki muszą być zoptymalizowane pod kątem nowych procesorów o tej samej architekturze?

39

Po wydaniu nowej generacji procesorów większość witryn informuje, że silniki i programy do gier muszą być zoptymalizowane pod kątem nowego sprzętu. Nie bardzo rozumiem dlaczego. Procesor zwykle ma architekturę, która określa, jakiego rodzaju zestawu instrukcji używa. Obecnie wszyscy używamy amd_x86_64. Dlaczego jakikolwiek program lub kompilator powinien być aktualizowany, jeśli wszystkie procesory korzystają z tej samej architektury? Na pewno istnieją funkcje W ramach potoku nowego procesora, który optymalizuje wykonanie kodu maszynowego, ale dlaczego sam kod maszynowy musiałby zostać zmieniony, jeśli architektura nie?

salbeira
źródło
Komentarze nie są przeznaczone do rozszerzonej dyskusji; ta rozmowa została przeniesiona do czatu .
Josh
14
„Potrzeba” jest złym sformułowaniem i bardziej marketingowym niż prawdą, podobnie jak np. Windows musi obsługiwać pewną nową generację procesorów (lub nie działa, jak w przypadku Windows 7, co w zasadzie działałoby idealnie dobrze z np. Ryzen, z wyjątkiem zużywania 3-4% więcej mocy niż to konieczne). To strojenie polega tylko na próbie wyciśnięcia nieco więcej z procesora, zbliżając się do maksimum. Realistycznie, możesz być w stanie uzyskać ogółem 1-2% w nie budzących sprzeciwu przykładach z powodu różnych harmonogramów i korzystania z kilku nowych instrukcji.
Damon
2
Tylko dlatego, że dwa procesory mogą wykonywać te same operacje, co nie znaczy, że operacje mają taką samą wydajność na obu procesorach ...
Mehrdad
Zobacz moje powiązane pytanie na temat przepełnienia stosu: jak faktycznie działa mtune?
Marc.2377,

Odpowiedzi:

54

Ponieważ różne generacje tej samej architektury mogą mieć różne zestawy instrukcji .

Na przykład, rozszerzenia SIMD Streaming są prawdopodobnie najbardziej znanym zestawem instrukcji x86, ale mimo to, mimo że istnieje tylko jedna architektura x86, istnieje SSE, SSE2, SSE3 i SSE4.

Każda z tych generacji może zawierać nowe instrukcje, które zapewniają szybsze sposoby wykonywania niektórych operacji. Przykładem odpowiednim dla gier może być instrukcja dot produktu.

Jeśli więc silnik gry zostanie skompilowany dla poprzedniej generacji architektury, nie będzie obsługiwał tych nowszych instrukcji. Podobnie może być konieczna optymalizacja silnika pod kątem nowszych instrukcji; Na przykład SSE4 obsługuje instrukcje dot produktu, które działają na danych typu tablica struktur. Optymalizacja, która mogłaby skorzystać z tych nowszych instrukcji, polegałaby na zmianie układu danych na tablicę struktur.

Maximus Minimus
źródło
1
@Panzercrisis - dziękuję za sugestię edycji. Żeby było jasne: pierwotne pytanie nie dotyczyło twojego kodu, dotyczyło kodu silnika, więc „optymalizacja własnego kodu” nie jest dobrą propozycją edycji. Podkreśliłem jednak, że muszę wyjaśnić, że kiedy mówiłem „optymalizuj”, miałem na myśli „optymalizuj kod silnika”, więc postanowiłem to podjąć.
Maximus Minimus,
37

Odpowiedź Maximusa jest poprawna, chcę tylko podać kolejny fragment historii:

Sam sprzęt zmienia się w sposób, w jaki należy zmienić sposób kodowania, niezależnie od nowo wprowadzonych instrukcji.

  • Zwiększona lub zmniejszona ilość pamięci podręcznej oznacza, że ​​musisz mniej lub bardziej martwić się o optymalizację pamięci podręcznej / unieważnienie pamięci podręcznej. Więcej pamięci podręcznej oznacza, że ​​przy małych danych można mniej skupić się na upewnieniu się, że dane są ciągłe i nie występują problemy z wydajnością. Mniej pamięci podręcznej oznacza, że ​​może to być problem, a bardzo mała pamięć podręczna oznacza, że ​​w przypadku niektórych dużych struktur danych nie będzie to miało żadnego znaczenia.

  • Nowe poziomy pamięci podręcznej oznaczają, że musisz przemyśleć, w jaki sposób organizujesz jeszcze większe zestawy danych (L1, L2, L3 vs L4).

  • Więcej rdzeni oznacza, że ​​musisz pomyśleć o tym, jak zamierzasz tworzyć aplikacje wielowątkowe i jak Twoja aplikacja skaluje się w środowisku wieloprocesowym.

  • Szybsze zegary oznaczają, że musisz zacząć myśleć o opóźnieniu pamięci bardziej niż o obliczeniach procesora jako wąskim gardle twojego systemu.

  • Liczba układów FPU w systemie może już nie odpowiadać liczbie liczb całkowitych ALU na rdzeń (AMD miał / ma takie architektury).

  • Liczba cykli zegara potrzebnych do obliczenia operacji zmniejszyła się lub wzrosła.

  • Zmieniła się liczba dostępnych rejestrów.

Wszystko to ma bardzo realny wpływ na wydajność programów, które przyjęły założenia dotyczące architektury bazowej w poprzednim sprzęcie z tym samym ISA, zarówno pozytywnym, jak i negatywnym.

whn
źródło
„Zwiększony lub zmniejszony poziom pamięci podręcznej oznacza, że ​​musisz mniej martwić się o spójność pamięci podręcznej.” - praktycznie każdy procesor jest spójny z pamięcią podręczną. Masz na myśli fałszywe udostępnianie? Nawet praktycznie każda linia CPU $ ma prawie zawsze 64 B ...
Maciej Piechotka
1
Maciej właśnie przyjmował twoje zdanie na temat spójności pamięci podręcznej :) Prawdopodobnie miałeś na myśli „optymalizację pamięci podręcznej” lub coś w tym rodzaju. Spójność pamięci podręcznej to zdolność systemu do utrzymywania spójnego widoku pamięci w sposób przejrzysty dla oprogramowania, nawet w obecności N niezależnych pamięci podręcznych. Jest to całkowicie prostopadłe do wielkości. TBH to stwierdzenie nie jest tak naprawdę istotne, ale twoja odpowiedź (szczególnie punkty 5 i 6) odpowiada na pytanie lepiej niż przyjęte IMO :) Być może podkreślenie różnicy między architekturą a u-architekturą sprawi, że będzie się bardziej wyróżniać.
Margaret Bloom
4
„jak rozmnażanie, które zajmuje więcej czasu niż dodawanie, podczas gdy jak dzisiaj na współczesnych wywiadach i CPUS, zajmuje to tyle samo czasu” To nie wszystko prawda. W architekturach potokowych musisz rozróżnić opóźnienie (gdy wynik jest gotowy) i przepustowość (ile możesz zrobić na cykl). Ponadto w nowoczesnych procesorach Intel ma przepustowość 4 i opóźnienie 1. Multiply ma przepustowość 1 i opóźnienie 3 (lub 4). Są to rzeczy, które zmieniają się z każdą architekturą i wymagają optymalizacji. Np. pdepTrwa 1 cykl na Intel, ale 6 na Ryzen, więc może nie chcieć używać go na Ryzen.
Christoph,
2
@ Clearer Wiem, że mówimy tutaj o procesorach, ale nigdy nie programowałeś dla procesorów graficznych, prawda? Ten sam kod daje tak bardzo różne wyniki w wydajności, że często jesteś zmuszony wziąć pod uwagę możliwości sprzętowe w CUDA. Stąd pochodzę z tego, rozmiar pamięci podręcznej (pamięć współdzielona, ​​zarządzana pamięć podręczna L1) faktycznie musi być wzięty pod uwagę przy tworzeniu kodu dla czegoś w CUDA.
kiedy
2
@ Christoph jest poprawny. Test porównawczy, który łączysz, dotyczy pętli nad tablicą c[i] = a[i] OP b[i](tj. 2 obciążenia i 1 pamięć na operację), więc czasy są zdominowane przez przepustowość pamięci z powodu bardzo niskiej intensywności obliczeniowej. Rozmiar tablicy nie jest pokazywany, więc IDK, jeśli pasuje do L1D. ( gcc4.9 -Ofastnajprawdopodobniej automatycznie wektoryzują te pętle, więc nawet nie mierzysz kosztów normalnych operacji skalarnych jako części złożonego kodu liczb całkowitych). Pierwszy wiersz tej strony jest WAŻNY: Przydatne informacje zwrotne ujawniły, że niektóre z tych środków mają poważne wady. Nadchodzi ważna aktualizacja .
Peter Cordes,
2

Nawet poza poważnymi zmianami, takimi jak obsługa nowych instrukcji, producenci mikroprocesorów stale modyfikują swoje projekty, aby poprawić wydajność, a każdy nowy projekt może mieć inną wydajność względną dla każdej instrukcji lub techniki. Być może napisałeś starannie zoptymalizowany kod bez rozgałęzień dla Modelu X, ale Model Y ma ulepszoną funkcję przewidywania rozgałęzień, która zmniejsza karę za nieprzewidywalność dla wersji bez rozgałęzień kodu (co również uwalnia rejestr do użycia w innym miejscu) . Być może Model Y obsługuje większą równoległość pewnej instrukcji o dużym opóźnieniu, tak że teraz rozwinięta pętla tej instrukcji zapewnia lepszą przepustowość, podczas gdy na Modelu X krótsza sekwencja była lepsza.

Każdy problem można rozwiązać na wiele sposobów, a każdy program to zbiór kompromisów i alokacji zasobów, od punktu optymalizacji. Nawet niewielkie zmiany w dostępności tych zasobów lub koszcie danego fragmentu kodu w odniesieniu do tych zasobów mogą mieć efekt kaskadowy, który daje znaczną przewagę wydajności dla jednego fragmentu kodu. Nawet jeśli ulepszony układ ma „więcej wszystkiego”, o ile więcej z każdej rzeczy może zmienić równowagę.

Hobbs
źródło