Gdy obliczenia o ograniczonej przepustowości pamięci są wykonywane w środowiskach pamięci współużytkowanej (np. Wątkowych przez OpenMP, Pthreads lub TBB), pojawia się dylemat, jak zapewnić, aby pamięć była prawidłowo rozdzielona na pamięć fizyczną , tak aby każdy wątek w większości uzyskiwał dostęp do pamięci na „lokalna” magistrala pamięci. Chociaż interfejsy nie są przenośne, większość systemów operacyjnych ma sposoby ustawiania powinowactwa wątków (np. W pthread_setaffinity_np()
wielu systemach POSIX, sched_setaffinity()
Linux, SetThreadAffinityMask()
Windows). Istnieją również biblioteki takie jak hwloc do określania hierarchii pamięci, ale niestety większość systemów operacyjnych nie zapewnia jeszcze sposobów ustawiania zasad pamięci NUMA. Linux jest godnym uwagi wyjątkiem, z libnumaumożliwianie aplikacji do manipulowania polityką pamięci i migracją stron przy ich szczegółowości (w głównej wersji od 2004 roku, a więc szeroko dostępne). Inne systemy operacyjne oczekują, że użytkownicy będą przestrzegać domyślnej zasady „pierwszego dotknięcia”.
Praca z zasadą „pierwszego dotknięcia” oznacza, że osoba dzwoniąca powinna tworzyć i rozpowszechniać wątki z dowolnym powinowactwem, którego zamierzają użyć później, kiedy po raz pierwszy pisze do świeżo przydzielonej pamięci. (Bardzo niewiele systemów jest skonfigurowanych w taki sposób, że malloc()
faktycznie wyszukuje strony, po prostu obiecuje je znaleźć, gdy zostaną faktycznie uszkodzone, być może przez różne wątki.) Oznacza to, że przydział calloc()
lub natychmiastowe zainicjowanie pamięci po użyciu przydziału memset()
jest szkodliwe, ponieważ może powodować błędy cała pamięć na szynę pamięci rdzenia, na którym działa wątek alokujący, co prowadzi do najmniejszego pasma przepustowości pamięci, gdy pamięć jest dostępna z wielu wątków. To samo dotyczy new
operatora C ++ , który nalega na zainicjowanie wielu nowych alokacji (npstd::complex
). Kilka uwag na temat tego środowiska:
- Alokacja może być „kolektywna dla wątków”, ale teraz alokacja staje się mieszana w modelu wątków, co jest niepożądane w przypadku bibliotek, które mogą być zmuszone do interakcji z klientami używającymi różnych modeli wątków (być może każda z własnymi pulami wątków).
- RAII jest uważane za ważną część idiomatycznego C ++, ale wydaje się być aktywnie szkodliwe dla wydajności pamięci w środowisku NUMA. Umieszczania
new
można używać z pamięcią przydzieloną przezmalloc()
lub z procedurlibnuma
, ale zmienia to proces przydzielania (który moim zdaniem jest konieczny). - EDYCJA: Moje wcześniejsze stwierdzenie o operatorze
new
było niepoprawne, może obsługiwać wiele argumentów, patrz odpowiedź Chetana. Uważam, że nadal istnieje obawa, aby biblioteki lub kontenery STL używały określonego powinowactwa. Wiele pól może być spakowanych i może być niewygodne upewnienie się, że np.std::vector
Realokacja następuje przy aktywnym poprawnym menedżerze kontekstu. - Każdy wątek może alokować i uszkadzać własną pamięć prywatną, ale indeksowanie do sąsiednich regionów jest bardziej skomplikowane. (Rozważ rzadki iloczyn macierzowo-wektorowy z rzędem partycji macierzy i wektorów; indeksowanie nie posiadanej części x wymaga bardziej skomplikowanej struktury danych, gdy x nie jest ciągły w pamięci wirtualnej.)
Czy jakieś rozwiązania dotyczące alokacji / inicjalizacji NUMA są uważane za idiomatyczne? Czy pominąłem inne krytyczne błędy?
(Nie mam na myśli mojego C ++ przykłady sugerować nacisk na ten język, jednak C ++ język koduje pewne decyzje o zarządzaniu pamięcią, że język jak C nie, więc nie wydaje się być większy opór, kiedy sugeruje, że programistów C ++ zrobić ci rzeczy inaczej.)
źródło
Ta odpowiedź jest odpowiedzią na dwa nieporozumienia związane z C ++ w pytaniu.
Nie jest to bezpośrednia odpowiedź na wspomniane problemy dotyczące wielu rdzeni. Wystarczy odpowiedzieć na komentarze, które klasyfikują programistów C ++ jako fanatyków C ++, aby utrzymać reputację;).
Do punktu 1. C ++ „nowy” lub przydział stosu nie nalega na inicjowanie nowych obiektów, niezależnie od tego, czy POD. Domyślny konstruktor klasy, zdefiniowany przez użytkownika, ponosi tę odpowiedzialność. Pierwszy kod poniżej pokazuje śmieci wydrukowane, czy klasa jest POD, czy nie.
Do punktu 2. C ++ pozwala na przeciążanie „nowego” wieloma argumentami. Drugi kod poniżej pokazuje taki przypadek przydzielania pojedynczych obiektów. Powinien dać pomysł i być może przydatny w obecnej sytuacji. operator new [] można również odpowiednio zmodyfikować.
// Kod dla punktu 1.
Kompilator Intela 11.1 pokazuje to wyjście (którym jest oczywiście niezainicjowana pamięć wskazywana przez „a”).
// Kod dla punktu 2.
źródło
std::complex
które są jawnie inicjowane.std::complex
?W deal.II mamy infrastrukturę oprogramowania do równoległego montażu na każdej komórce na wielu rdzeniach za pomocą bloków wątków (w zasadzie masz jedno zadanie na komórkę i musisz zaplanować te zadania na dostępnych procesorach - nie w ten sposób wdrożone, ale jest to ogólny pomysł). Problem polega na tym, że do lokalnej integracji potrzebujesz wielu tymczasowych (scratch) obiektów i musisz podać co najmniej tyle, ile jest zadań, które można uruchomić równolegle. Widzimy słabe przyspieszenie, prawdopodobnie dlatego, że gdy zadanie zostanie umieszczone na procesorze, chwyta on jeden ze zdrapanych obiektów, który zwykle będzie w pamięci podręcznej innego rdzenia. Mieliśmy dwa pytania:
(i) Czy to naprawdę powód? Kiedy uruchamiamy program w cachegrind, widzę, że używam zasadniczo takiej samej liczby instrukcji, jak podczas uruchamiania programu w jednym wątku, ale całkowity czas działania zgromadzony dla wszystkich wątków jest znacznie większy niż w jednym wątku. Czy to naprawdę dlatego, że ciągle winy pamięci podręcznej?
(ii) Jak mogę dowiedzieć się, gdzie jestem, gdzie znajdują się wszystkie obiekty scratch i które obiekty scratch muszę wziąć, aby uzyskać dostęp do tego, który jest gorący w pamięci podręcznej mojego rdzenia?
Ostatecznie nie znaleźliśmy odpowiedzi na żadne z tych rozwiązań i po kilku pracach zdecydowaliśmy, że brakuje nam narzędzi do zbadania i rozwiązania tych problemów. Wiem, jak przynajmniej w zasadzie rozwiązać problem (ii) (a mianowicie, używając obiektów lokalnych wątków, zakładając, że wątki pozostają przypięte do rdzeni procesora - kolejna hipoteza, która nie jest trywialna do przetestowania), ale nie mam narzędzi do testowania problemu (ja).
Z naszego punktu widzenia radzenie sobie z NUMA jest wciąż nierozwiązanym pytaniem.
źródło
Oprócz hwloc istnieje kilka narzędzi, które mogą raportować o środowisku pamięci klastra HPC i które można wykorzystać do ustawienia różnych konfiguracji NUMA.
Poleciłbym LIKWID jako jedno z takich narzędzi, ponieważ unika ono podejścia opartego na kodzie, pozwalającego na przykład przypiąć proces do rdzenia. Takie podejście do oprzyrządowania w celu rozwiązania konfiguracji pamięci specyficznej dla maszyny pomoże zapewnić przenośność kodu między klastrami.
Można znaleźć krótką prezentację opisującą go z ISC'13 „ LIKWID - lekkie narzędzia wydajności ”, a autorzy opublikowali artykuł na temat Arxiv „ Najlepsze praktyki inżynierii wydajności wspomaganej przez HPM na temat współczesnego procesora wielordzeniowego ”. W tym artykule opisano podejście do interpretacji danych z liczników sprzętowych w celu opracowania wydajnego kodu specyficznego dla architektury komputera i topologii pamięci.
źródło