Część trzecia artykułu o poleceniach linux poprzednio mieliśmy:
A w tym odcinku porozmawiamy sobie jak z naszego systemu wyciągnąc wiecej :)
numactl
Polecenie numactl jest potężnym narzędziem dostępnym w systemach Linux, które pozwala na kontrolowanie alokacji pamięci i przydzielania procesów w architekturze Non-Uniform Memory Access (NUMA). W czasach rosnącej złożoności i rozbudowy wieloprocesorowych systemów komputerowych, narzędzie to stanowi kluczowe rozwiązanie dla administratorów systemów oraz programistów, którzy pragną zoptymalizować wydajność swoich aplikacji.
Czym jest polecenie numactl i do czego służy?
Polecenie numactl to narzędzie wykorzystywane w systemach Linux do kontroli alokacji pamięci i zarządzania procesami w architekturze NUMA. W skrócie, NUMA to architektura stosowana w wieloprocesorowych systemach komputerowych, w których czas dostępu do pamięci zależy od położenia procesora względem pamięci. Węzły NUMA zawierają procesor(y) oraz lokalną pamięć, a czas dostępu do pamięci w węźle jest krótszy niż do pamięci w innym węźle.
Polecenie numactl pozwala na ustawienie polityki alokacji pamięci dla danego procesu oraz przydzielenie procesu do określonego węzła NUMA. Dzięki temu możliwe jest lepsze wykorzystanie dostępnych zasobów oraz minimalizacja opóźnień wynikających z przesyłania danych między węzłami.
Historia polecenia numactl
Wraz z rozwojem technologii komputerowych, zwłaszcza w dziedzinie wieloprocesorowych systemów, pojawiła się potrzeba lepszego zarządzania zasobami pamięci. Początkowo, systemy komputerowe stosowały architekturę Uniform Memory Access (UMA), gdzie czas dostępu do pamięci był taki sam dla wszystkich procesorów. W miarę zwiększania liczby procesorów w systemach, UMA zaczęło prowadzić do problemów z wydajnością i skalowalnością.
W związku z tym, w latach 90. XX wieku powstała architektura Non-Uniform Memory Access (NUMA) jako odpowiedź na rosnące wyzwania związane z zarządzaniem pamięcią w wieloprocesorowych systemach. Początkowo, NUMA była stosowana głównie w superkomputerach oraz dużych systemach mainframe, jednak w miarę upływu czasu, coraz więcej serwerów i stacji roboczych zaczęło korzystać z tej architektury, aby poprawić swoją wydajność.
W związku z tym, istniała potrzeba stworzenia narzędzi, które umożliwiłyby efektywne zarządzanie zasobami pamięci w systemach NUMA. Pierwsze wersje polecenia numactl pojawiły się na początku lat 2000. W kolejnych latach narzędzie było rozwijane, dopracowywane i dostosowywane do różnych dystrybucji Linuxa.
Przełączniki polecenia
Wraz z rozwojem narzędzia numactl, programiści wprowadzili różne opcje pozwalające na bardziej precyzyjne zarządzanie węzłami NUMA. Niektóre z tych opcji obejmują:
--cpunodebind=<nodes>: Ta opcja pozwala na przydzielenie procesu do określonego węzła (lub węzłów) NUMA. Proces będzie miał dostęp jedynie do CPU związanych z tymi węzłami. Wartość <nodes> może być pojedynczym węzłem (np. --cpunodebind=0) lub listą węzłów (np. --cpunodebind=0,1,2).
--membind=<nodes>: Za pomocą tej opcji można określić, z których węzłów NUMA proces będzie mógł alokować pamięć. Wartość <nodes> może być pojedynczym węzłem (np. --membind=0) lub listą węzłów (np. --membind=0,1,2).
--preferred=<node>: Opcja ta pozwala na ustawienie preferowanego węzła NUMA dla alokacji pamięci. System stara się alokować pamięć z tego węzła, ale może korzystać z innych węzłów, jeśli to konieczne. Wartość <node> powinna być pojedynczym węzłem (np. --preferred=0).
--interleave=<nodes>: Ta opcja rozkłada alokacje pamięci równomiernie na wskazanych węzłach NUMA. Może to poprawić wydajność w niektórych przypadkach, gdy równomierne rozłożenie pamięci jest korzystne. Wartość <nodes> może być pojedynczym węzłem (np. --interleave=0) lub listą węzłów (np. --interleave=0,1,2).
--localalloc: Domyślna polityka alokacji pamięci, która polega na próbie alokowania pamięci lokalnie dla danego procesu. Jeśli lokalna pamięć jest niewystarczająca, system może korzystać z pamięci zdalnych węzłów.
--physcpubind=<cpus>: Ta opcja pozwala na przydzielenie procesu do określonego procesora (lub procesorów) fizycznego. Wartość <cpus> może być pojedynczym procesorem (np. --physcpubind=0) lub listą procesorów (np. --physcpubind=0,1,2).
--show: Wyświetla aktualne ustawienia NUMA dla danego procesu.
--hardware: Wyświetla informacje o sprzęcie NUMA, takie jak liczba węzłów, ich rozmiary oraz topologia połączeń między nimi.
--length=<size>: Opcja stosowana w połączeniu z --dump lub --dump-nodes, aby określić rozmiar obszaru pamięci, który ma zostać zrzutowany. Wartość <size> powinna być podana w bajtach, kilobajtach (z dopiskiem K), megabajtach (z dopiskiem M) lub gigabajtach (z dopiskiem G). Na przykład, --length=1G oznacza, że rozmiar obszaru pamięci do zrzucenia wynosi 1 gigabajt.
--offset=<offset>: Opcja stosowana w połączeniu z --dump lub --dump-nodes, aby określić przesunięcie początkowe obszaru pamięci, który ma zostać zrzutowany. Wartość <offset> powinna być podana w bajtach, kilobajtach (z dopiskiem K), megabajtach (z dopiskiem M) lub gigabajtach (z dopiskiem G).
--dump i --dump-nodes=<nodes>: Te opcje pozwalają na zrzut obszaru pamięci na standardowe wyjście. Opcja --dump zrzuci całą pamięć, podczas gdy --dump-nodes zrzuci pamięć tylko z określonych węzłów NUMA. Wartość <nodes> może być pojedynczym węzłem (np. --dump-nodes=0) lub listą węzłów (np. --dump-nodes=0,1,2).
--touch i --touch-nodes=<nodes>: Opcje te powodują "dotknięcie" (inicjalizację) obszaru pamięci przed jego użyciem przez proces. Opcja --touch dotyka całej pamięci, natomiast --touch-nodes dotyka tylko pamięci z określonych węzłów NUMA. Wartość <nodes> może być pojedynczym węzłem (np. --touch-nodes=0) lub listą węzłów (np. --touch-nodes=0,1,2).
--huge i --huge=<size>: Opcje te pozwalają na alokowanie dużych stron pamięci (ang. huge pages) dla procesu. Opcja --huge alokuje domyślny rozmiar dużych stron pamięci, natomiast --huge=<size> pozwala na określenie rozmiaru dużych stron. Wartość <size> powinna być podana w megabajtach (z dopiskiem M) lub gigabajtach (z dopiskiem G).
--shm=<key> i --shm=<key>:<size>: Opcje te alokują obszar pamięci współdzielonej (ang. shared memory) związany z określonym kluczem <key>. Opcja --shm=<key> alokuje domyślny rozmiar pamięci współdzielonej, natomiast --shm=<key>:<size> pozwala na określenie rozmiaru pamięci współdzielonej. Wartość <size> powinna być podana w bajtach, kilobajtach (z dopiskiem K), megabajtach (z dopiskiem M) lub gigabajtach (z dopiskiem G).
--file=<path> i --file=<path>:<size>: Opcje te alokują obszar pamięci oparty na pliku o określonej ścieżce <path>. Opcja --file=<path> alokuje domyślny rozmiar pamięci na podstawie pliku, natomiast --file=<path>:<size> pozwala na określenie rozmiaru pamięci. Wartość <size> powinna być podana w bajtach, kilobajtach (z dopiskiem K), megabajtach (z dopiskiem M) lub gigabajtach (z dopiskiem G).
--roundrobin: Opcja ta pozwala na równomierne rozłożenie alokacji pamięci na wszystkich dostępnych węzłach NUMA. Jest to równoznaczne z użyciem opcji --interleave=all.
--strict: Ta opcja sprawia, że numactl zakończy działanie z błędem, jeśli nie można zastosować żądanej polityki alokacji pamięci. Domyślnie, numactl może korzystać z innych polityk alokacji, jeśli żądana polityka nie może być zastosowana.
--singleversion: Opcja ta sprawia, że numactl używa tylko jednej wersji biblioteki libnuma, nawet jeśli dostępne są różne wersje. Jest to przydatne w przypadku, gdy zachodzi potrzeba kontroli nad wersją biblioteki używaną przez numactl.
--all i --all=<size>: Opcje te alokują pamięć na wszystkich dostępnych węzłach NUMA. Opcja --all alokuje domyślny rozmiar pamięci na każdym węźle, natomiast --all=<size> pozwala na określenie rozmiaru pamięci. Wartość <size> powinna być podana w bajtach, kilobajtach (z dopiskiem K), megabajtach (z dopiskiem M) lub gigabajtach (z dopiskiem G).
Te opcje można łączyć w celu uzyskania bardziej zaawansowanego zarządzania pamięcią i procesami w systemach NUMA. Pamiętaj, że niektóre opcje mogą być niekompatybilne ze sobą, więc warto zwrócić uwagę na to, które opcje są używane jednocześnie. Przed rozpoczęciem pracy z poleceniem numactl, warto zapoznać się z jego dokładną dokumentacją oraz przemyśleć strategię optymalizacji zasobów w systemie NUMA.
Zastosowanie polecenia numactl w praktyce
Polecenie numactl jest często używane przez administratorów systemów, programistów oraz inżynierów ds. wydajności, którzy pragną zoptymalizować wydajność swoich aplikacji w systemach NUMA. Narzędzie to pozwala na kontrolowanie, w jaki sposób procesy są przydzielane do poszczególnych węzłów NUMA, co pozwala lepiej wykorzystać zasoby pamięci.
Przykład użycia polecenia numactl może wyglądać następująco:
W powyższym przykładzie, polecenie numactl uruchamia program my_program, przydzielając go do węzła NUMA 0 oraz alokując pamięć tylko z tego węzła.
Innym przykładem może być równomierne rozkładanie alokacji pamięci na dwóch węzłach NUMA:
Warto zauważyć, że korzystanie z polecenia numactl może przynieść istotne korzyści wydajnościowe, ale wymaga także doświadczenia i zrozumienia działania systemów NUMA. Niewłaściwe użycie narzędzia może prowadzić do nieoptymalnego przydzielania zasobów, co skutkować będzie spadkiem wydajności.
Współczesne systemy operacyjne, takie jak Linux, starają się automatycznie optymalizować alokację procesów i zasobów w systemach NUMA. Mimo to, w niektórych przypadkach, ręczna interwencja z wykorzystaniem polecenia numactl może być konieczna w celu uzyskania najlepszej możliwej wydajności.
Korzystanie z NUMA może wpłynąć na wydajność systemu, szczególnie w przypadku dysków twardych i interfejsów sieciowych. Przyjrzymy się także, kiedy przypisanie danego programu zapisującego lub wysyłającego dane przez konkretne urządzenie przypięte do danego węzła NUMA może być korzystne.
Redukcja opóźnień: Przydzielenie procesów do węzłów NUMA, do których są bezpośrednio podłączone urządzenia, takie jak dyski twarde lub interfejsy sieciowe, może zmniejszyć opóźnienia. Dzięki temu procesy mają szybszy dostęp do zasobów, co przekłada się na lepszą wydajność.
Zwiększenie przepustowości: Przydzielenie procesów do węzłów NUMA zgodnie z lokalizacją urządzeń może również zwiększyć przepustowość. Poprawia to prędkość transferu danych między procesami a urządzeniami, co przekłada się na szybsze działanie systemu.
Lepsze wykorzystanie zasobów: Optymalna alokacja NUMA umożliwia lepsze wykorzystanie zasobów sprzętowych. Dzięki temu system może obsługiwać więcej zadań jednocześnie, co zwiększa wydajność.
Dyski twarde: Alokowanie NUMA w przypadku dysków twardych może poprawić wydajność operacji wejścia/wyjścia. Przydzielenie procesu do węzła NUMA, który zarządza dyskiem twardym, może zmniejszyć opóźnienia i zwiększyć prędkość transferu danych. Na przykład, jeśli proces zapisuje dane na dysku twardym podłączonym do węzła NUMA 0, alokacja procesu do tego samego węzła NUMA może przyspieszyć operacje zapisu i odczytu.
Interfejsy sieciowe: Podobnie jak w przypadku dysków twardych, alokowanie NUMA w przypadku interfejsów sieciowych może również wpłynąć na wydajność systemu. Przydzielenie procesu do węzła NUMA zarządzającego interfejsem sieciowym może poprawić przepustowość sieci i zmniejszyć opóźnienia. Na przykład, jeśli proces wysyła dane przez interfejs sieciowy podłączony do węzła NUMA 1, przypisanie procesu do tego samego węzła NUMA może przyspieszyć operacje wysyłania i odbierania danych.
Kiedy przypisać program do urządzenia przypiętego do danego węzła NUMA?
Przypisanie programu do urządzenia przypiętego do danego węzła NUMA może być korzystne w następujących sytuacjach:
Gdy program intensywnie korzysta z zasobów, takich jak dyski twarde lub interfejsy sieciowe. Przypisanie programu do węzła NUMA zarządzającego tymi urządzeniami może poprawić jego wydajność.
W przypadku programów, które wymagają szybkiego przesyłania danych między procesami a urządzeniami, przypisanie ich do odpowiedniego węzła NUMA może zmniejszyć opóźnienia i zwiększyć przepustowość.
Gdy system ma nierównomiernie rozłożone obciążenie między węzłami NUMA, przypisanie programów do węzłów zgodnie z lokalizacją urządzeń może pomóc zrównoważyć obciążenie i poprawić wydajność systemu.
W systemie Linux istnieje wiele narzędzi, które pozwalają kontrolować na którym wątku procesora uruchamiany jest program, mamy jeszcze:
taskset
Polecenie taskset to narzędzie dostępne w systemach Linux, które pozwala zarządzać afiliacją procesów i wątków do rdzeni procesora. Dzięki temu narzędziu można kontrolować, na których rdzeniach procesora uruchamiane są programy, co pozwala na lepsze zarządzanie zasobami systemu i optymalizację wydajności.
Przełączniki taskset
-c, --cpu-list: Lista rdzeni, na których ma być uruchomiony proces. Przyjmuje listę oddzieloną przecinkami, zakresy oddzielone myślnikami, lub kombinacje tych dwóch.
-p, --pid: Zamiast uruchamiać nowy proces, taskset zostanie użyty do zmiany afiliacji rdzenia istniejącego procesu o podanym numerze PID.
-a, --all-tasks: Przeskalowanie afiliacji rdzenia dla wszystkich wątków istniejącego procesu o podanym numerze PID.
-h, --help: Wyświetla pomoc dotyczącą użycia polecenia taskset.
Przykłady użycia polecenia taskset
Uruchamianie programu na konkretnych rdzeniach
Jeśli chcesz uruchomić program na określonych rdzeniach procesora, użyj przełącznika -c:
W powyższym przykładzie program zostanie uruchomiony na rdzeniach 0 i 1.
Uruchamianie programu na zakresie rdzeni
Możesz także podać zakres rdzeni za pomocą myślnika:
W powyższym przykładzie program zostanie uruchomiony na rdzeniach 2 i 3.
Zmiana afiliacji rdzenia dla istniejącego procesu
Jeśli chcesz zmienić afiliację rdzenia dla istniejącego procesu o określonym numerze PID, użyj przełącznika -p:
W powyższym przykładzie afiliacja rdzenia dla procesu o numerze PID 12345 zostanie zmieniona na rdzenie 0 i 2.
Wyświetlanie afiliacji rdzenia dla istniejącego procesu
Aby sprawdzić, na których rdzeniach działa istniejący proces, użyj przełącznika -p bez podawania listy rdzeni:
W powyższym przykładzie zostanie wyświetlona afiliacja rdzenia dla procesu o numerze PID 12345.
Zmiana afiliacji rdzenia dla wszystkich wątków istniejącego procesu
Jeśli chcesz zmienić afiliację rdzenia dla wszystkich wątków istniejącego procesu, użyj przełącznika -a:
W powyższym przykładzie afiliacja rdzenia dla wszystkich wątków procesu o numerze PID 12345 zostanie zmieniona na rdzenie 0 i 2.
Uruchamianie programu na kombinacji rdzeni
Możesz także podać kombinację listy i zakresu rdzeni:
W powyższym przykładzie program zostanie uruchomiony na rdzeniach 3, 4 i 6.
Podsumowując, polecenie taskset pozwala na kontrolowanie afiliacji rdzeni procesora dla nowo uruchamianych i istniejących procesów. Może to być użyteczne w przypadku optymalizacji wydajności, szczególnie w środowiskach, w których równoczesne wykonywanie wielu zadań wymaga zarządzania zasobami procesora. Dzięki temu narzędziu można ręcznie kontrolować wykorzystanie rdzeni procesora, dostosowując je do indywidualnych potrzeb i celów.
Warto też spojrzeć na polecenia cgroups i schedtool na których można uzyskać podobny efekt
cgroups
Cgroups (control groups) to mechanizm jądra Linux, który pozwala na ograniczenie i izolowanie zasobów systemowych dla procesów.
Przykład 1:
Utworzenie grupy kontrolnej "myGroup".
Przykład 2:
Przypisanie rdzenia 0 do grupy kontrolnej "myGroup".
Przykład 3:
Dodanie bieżącego procesu do grupy kontrolnej "myGroup".
Przykład 4:
Uruchomienie programu w grupie kontrolnej "myGroup"
Przykład 5:
Usuwanie grupy kontrolnej "myGroup".
schedtool
Polecenie schedtool pozwala zarządzać harmonogramem i afiliacją rdzeni dla procesów.
Przykład 1:
Uruchomienie programu na rdzeniach 0 i 1 (maska bitowa: 0b11 = 0x3).
Przykład 2:
Uruchomienie programu na rdzeniach 0 i 2 (maska bitowa: 0b101 = 0x5).
Przykład 3:
Przypisanie istniejącego procesu o PID 12345 do rdzeni 0 i 1.
Przykład 4:
Wyświetlanie aktualnej afiliacji rdzeni dla procesu o PID 12345.
Przykład 5:
Uruchomienie programu na rdzeniach 0 i 3 (maska bitowa: 0b1001 = 0x9).
Comments