Odpowiedzi:
To dość intuicyjne:
Program jest związany z procesorem, jeśli działałby szybciej, gdyby procesor był szybszy, tzn. Spędza większość czasu po prostu wykorzystując procesor (wykonując obliczenia). Program, który oblicza nowe cyfry π, zwykle jest związany z procesorem, to po prostu chrupiące liczby.
Program jest powiązany z operacjami we / wy, jeśli działałby szybciej, gdyby podsystem we / wy był szybszy. To, który dokładnie system I / O ma na myśli, może się różnić; Zazwyczaj kojarzę go z dyskiem, ale oczywiście powszechna jest także praca w sieci lub komunikacja. Program, który przegląda ogromny plik w poszukiwaniu niektórych danych, może zostać powiązany z operacjami wejścia / wyjścia, ponieważ wąskim gardłem jest wówczas odczyt danych z dysku (w rzeczywistości ten przykład jest obecnie trochę staromodny z setkami MB / s przychodzące z dysków SSD).
Ograniczenie procesora oznacza szybkość, z jaką proces postępuje, jest ograniczona szybkością procesora. Zadanie, które wykonuje obliczenia na małym zestawie liczb, na przykład mnożąc małe macierze, prawdopodobnie będzie związane z procesorem.
Ograniczenie We / Wy oznacza szybkość, z jaką proces postępuje, jest ograniczona prędkością podsystemu We / Wy. Zadanie przetwarzające dane z dysku, na przykład zliczanie liczby wierszy w pliku, może być prawdopodobnie związane z operacjami we / wy.
Powiązanie z pamięcią oznacza szybkość, z jaką proces postępuje, jest ograniczona ilością dostępnej pamięci i szybkością dostępu do pamięci. Zadanie, które przetwarza duże ilości danych w pamięci, na przykład mnożenie dużych macierzy, prawdopodobnie będzie związane z pamięcią.
Ograniczona pamięć podręczna oznacza szybkość, z jaką postęp procesu jest ograniczony przez ilość i szybkość dostępnej pamięci podręcznej. Zadanie, które po prostu przetwarza więcej danych niż mieści się w pamięci podręcznej, zostanie powiązane z pamięcią podręczną.
Ograniczenie wejścia / wyjścia byłoby wolniejsze niż ograniczenie pamięci byłoby wolniejsze niż ograniczenie pamięci podręcznej byłoby wolniejsze niż ograniczenie procesora.
Rozwiązaniem problemu związania we / wy niekoniecznie jest uzyskanie większej ilości pamięci. W niektórych sytuacjach algorytm dostępu można zaprojektować z uwzględnieniem ograniczeń we / wy, pamięci lub pamięci podręcznej. Zobacz Cache Oblivious Algorytmy .
Wielowątkowość
W tej odpowiedzi zbadam jeden ważny przypadek użycia rozróżnienia między pracą związaną z CPU a IO: podczas pisania kodu wielowątkowego.
Przykład związany z We / Wy pamięci RAM: Suma wektorowa
Rozważmy program sumujący wszystkie wartości jednego wektora:
#define SIZE 1000000000
unsigned int is[SIZE];
unsigned int sum = 0;
size_t i = 0;
for (i = 0; i < SIZE; i++)
/* Each one of those requires a RAM access! */
sum += is[i]
Równoważenie tego przez równe podzielenie macierzy dla każdego z rdzeni ma ograniczoną przydatność na popularnych współczesnych komputerach stacjonarnych.
Na przykład na moim Ubuntu 19.04, laptop Lenovo ThinkPad P51 z procesorem: procesor Intel Core i7-7820HQ (4 rdzenie / 8 wątków), pamięć RAM: 2x Samsung M471A2K43BB1-CRC (2x 16GiB) Otrzymuję wyniki takie jak to:
Zauważ jednak, że istnieje duża wariancja między uruchomieniami. Ale nie mogę znacznie zwiększyć rozmiaru tablicy, ponieważ mam już 8GiB i nie mam dzisiaj ochoty na statystyki dla wielu przebiegów. Wydawało się to jednak typowym uruchomieniem po wykonaniu wielu ręcznych uruchomień.
Kod testu porównawczego:
pthread
Kod źródłowy POSIX C. użyty na wykresie.
A oto wersja C ++, która daje analogiczne wyniki.
Nie znam wystarczającej architektury komputerowej, aby w pełni wyjaśnić kształt krzywej, ale jedno jest jasne: obliczenia nie stają się 8-krotnie szybsze, niż naiwnie oczekiwałem, ponieważ używam wszystkich moich 8 wątków! Z jakiegoś powodu 2 i 3 wątki były optymalne, a dodawanie kolejnych powoduje, że wszystko jest znacznie wolniejsze.
Porównaj to z pracą związaną z procesorem, która faktycznie staje się 8-krotnie szybsza: co oznaczają „rzeczywisty”, „użytkownik” i „sys” na wyjściu czasu (1)?
Powodem jest to, że wszystkie procesory współużytkują jedną szynę pamięci łączącą się z pamięcią RAM:
CPU 1 --\ Bus +-----+
CPU 2 ---\__________| RAM |
... ---/ +-----+
CPU N --/
więc magistrala pamięci szybko staje się wąskim gardłem, a nie procesorem.
Dzieje się tak, ponieważ dodanie dwóch liczb zajmuje jeden cykl procesora, odczyty pamięci zajmują około 100 cykli procesora w sprzęcie z 2016 roku.
Praca procesora wykonywana na bajt danych wejściowych jest zbyt mała i nazywamy to procesem związanym z We / Wy.
Jedynym sposobem na dalsze przyspieszenie tego obliczenia byłoby przyspieszenie dostępu do poszczególnych pamięci za pomocą nowego sprzętu pamięci, np . Pamięci wielokanałowej .
Na przykład aktualizacja do szybszego zegara procesora nie byłaby bardzo przydatna.
Inne przykłady
mnożenie macierzy jest zależne od procesora w pamięci RAM i procesorach graficznych. Dane wejściowe zawierają:
2 * N**2
liczby, ale:
N ** 3
mnożenia są wykonywane, i to wystarczy, aby równoległość była warta praktycznych dużych N.
Właśnie dlatego istnieją takie biblioteki mnożenia macierzy procesora równoległego, jak:
Wykorzystanie pamięci podręcznej ma duży wpływ na szybkość implementacji. Zobacz na przykład ten dydaktyczny przykład porównania GPU .
Zobacz też:
Networking jest prototypowym przykładem związanym z IO.
Nawet gdy wysyłamy jeden bajt danych, dotarcie do miejsca docelowego zajmuje dużo czasu.
Równoważenie małych żądań sieciowych, takich jak żądania HTTP, może zapewnić ogromny wzrost wydajności.
Jeśli sieć ma już pełną pojemność (np. Pobieranie torrenta), równoległość może jeszcze zwiększyć opóźnienie (np. Można załadować stronę internetową „w tym samym czasie”).
Fikcyjna operacja związana z procesorem C ++, która wymaga jednej liczby i często ją chrupie:
Sortowanie wydaje się być procesorem na podstawie następującego eksperymentu: Czy algorytmy równoległe C ++ 17 zostały już zaimplementowane? co pokazało czterokrotną poprawę wydajności dla sortowania równoległego, ale chciałbym również uzyskać bardziej teoretyczne potwierdzenie
Jak dowiedzieć się, czy jesteś związany procesorem czy we / wy
IO niezwiązane z pamięcią RAM, jak dysk, sieć ps aux
:, a następnie sprawdź jeśli CPU% / 100 < n threads
. Jeśli tak, jesteś związany We / Wy, np. Blokowanie read
tylko czeka na dane, a program planujący pomija ten proces. Następnie użyj innych narzędzi, takich jak sudo iotop
decyzja, które dokładnie jest problemem.
Lub, jeśli wykonanie jest szybkie i sparametryzujesz liczbę wątków, możesz to łatwo zauważyć, ponieważ time
wydajność poprawia się wraz ze wzrostem liczby wątków dla pracy związanej z procesorem: Co oznaczają słowa „rzeczywisty”, „użytkownik” i „sys” w wyjście czasu (1)?
Związany z RAM-IO: trudniej powiedzieć, ponieważ czas oczekiwania RAM jest uwzględniany w CPU%
pomiarach, patrz także:
Niektóre opcje:
GPU
Procesory graficzne mają wąskie gardło we / wy podczas pierwszego przesyłania danych wejściowych ze zwykłej pamięci RAM odczytywalnej przez procesor do GPU.
Dlatego procesory graficzne mogą być lepsze niż procesory tylko dla aplikacji związanych z procesorem.
Jednak po przesłaniu danych do GPU może działać na tych bajtach szybciej niż procesor, ponieważ GPU:
ma większą lokalizację danych niż większość systemów CPU, dzięki czemu dostęp do danych jest szybszy dla niektórych rdzeni niż dla innych
wykorzystuje równoległość danych i opóźnia poświęcenie, po prostu pomijając wszelkie dane, które nie są gotowe do natychmiastowej obsługi.
Ponieważ GPU musi działać na dużych równoległych danych wejściowych, lepiej jest po prostu przejść do następnych danych, które mogą być dostępne, zamiast czekać na pojawienie się bieżących danych i zablokować wszystkie inne operacje, tak jak procesor w większości przypadków
Dlatego procesor graficzny może być szybszy niż procesor, jeśli aplikacja:
Te opcje projektowania pierwotnie dotyczyły zastosowania renderowania 3D, których główne kroki są pokazane w Co to są shadery w OpenGL i do czego są nam potrzebne?
i doszliśmy do wniosku, że te aplikacje są związane z procesorem.
Wraz z pojawieniem się programowalnego GPGPU możemy zaobserwować kilka aplikacji GPGPU, które służą jako przykłady operacji związanych z procesorem:
Przetwarzanie obrazu za pomocą shaderów GLSL?
Lokalne operacje przetwarzania obrazu, takie jak filtr rozmycia, mają charakter wysoce równoległy.
Czy można zbudować mapę cieplną na podstawie danych punktowych z prędkością 60 razy na sekundę?
Wykreślanie wykresów mapy cieplnej, jeśli wykreślona funkcja jest wystarczająco złożona.
https://www.youtube.com/watch?v=fE0P6H8eK4I „Dynamika płynów w czasie rzeczywistym: procesor kontra GPU” Jesús Martín Berlanga
Rozwiązywanie równań różniczkowych cząstkowych, takich jak równanie Naviera Stokesa dynamiki płynów:
Zobacz też:
CPython Global Intepreter Lock (GIL)
W ramach krótkiego studium przypadku chcę zwrócić uwagę na globalną blokadę interpretera (GIL) w Pythonie: Co to jest globalna blokada tłumacza (GIL) w CPython?
Ten szczegół implementacji CPython uniemożliwia wielu wątkom Pythona wydajne korzystanie z pracy związanej z procesorem. Dokumenty CPython mówią:
Szczegóły implementacji CPython: W CPython, ze względu na globalną blokadę interpretera, tylko jeden wątek może wykonać kod Pythona jednocześnie (nawet jeśli niektóre biblioteki zorientowane na wydajność mogą obejść to ograniczenie). Jeśli chcesz, aby aplikacja lepiej wykorzystywała zasoby obliczeniowe maszyn wielordzeniowych, zalecamy użycie
multiprocessing
lubconcurrent.futures.ProcessPoolExecutor
. Jednak wątkowanie jest nadal odpowiednim modelem, jeśli chcesz uruchamiać wiele zadań związanych z operacjami we / wy jednocześnie.
Dlatego mamy tutaj przykład, w którym zawartość związana z procesorem nie jest odpowiednia, a związana jest z operacjami we / wy.
Ograniczenie procesora oznacza, że program ma wąskie gardło ze strony procesora lub jednostki centralnej, natomiast ograniczenie wejścia / wyjścia oznacza, że program jest wąskie gardło przez wejścia / wyjścia lub wejścia / wyjścia, takie jak odczyt lub zapis na dysk, sieć itp.
Ogólnie, optymalizując programy komputerowe, próbuje się znaleźć wąskie gardło i go wyeliminować. Wiedza o tym, że twój program jest związany z procesorem, pomaga, aby niepotrzebnie nie optymalizować czegoś innego.
[Przez „wąskie gardło” mam na myśli rzecz, która powoduje, że twój program działa wolniej niż w innym przypadku.]
Inny sposób sformułowania tego samego pomysłu:
Jeśli przyspieszenie procesora nie przyspieszyć swój program, to może być I / O związany.
Jeśli przyspieszenie operacji we / wy (np. Użycie szybszego dysku) nie pomaga, program może być związany z procesorem.
(Użyłem „może być”, ponieważ musisz wziąć pod uwagę inne zasoby. Pamięć jest jednym z przykładów).
Gdy program czeka na operacje we / wy (tj. Odczyt / zapis dysku lub odczyt / zapis sieciowy itp.), Procesor może wykonywać inne zadania, nawet jeśli program zostanie zatrzymany. Szybkość twojego programu będzie zależeć głównie od tego, jak szybko może nastąpić IO, a jeśli chcesz go przyspieszyć, musisz przyspieszyć I / O.
Jeśli twój program wykonuje wiele instrukcji programu i nie czeka na operacje wejścia / wyjścia, mówi się, że jest związany z procesorem. Przyspieszenie procesora przyspieszy działanie programu.
W obu przypadkach kluczem do przyspieszenia programu może nie być przyspieszenie sprzętu, ale optymalizacja programu w celu zmniejszenia potrzebnej liczby operacji wejścia / wyjścia lub procesora we / wy, podczas gdy procesor wymaga dużego obciążenia procesora rzeczy.
Powiązanie We / Wy odnosi się do stanu, w którym czas potrzebny do ukończenia obliczeń jest określany głównie przez okres oczekiwania na zakończenie operacji wejścia / wyjścia.
Jest to przeciwieństwo zadania związanego z procesorem. Ta okoliczność powstaje, gdy szybkość żądania danych jest mniejsza niż szybkość ich zużywania lub, innymi słowy, spędza się więcej czasu na żądaniu danych niż na ich przetwarzaniu.
Rdzeniem programowania asynchronicznego są obiekty Task i Task, które modelują operacje asynchroniczne. Są obsługiwane przez asynchronię i czekają na słowa kluczowe. Model jest w większości przypadków dość prosty:
W przypadku kodu związanego z operacjami we / wy oczekuje się operacji, która zwróci zadanie lub zadanie wewnątrz metody asynchronicznej.
W przypadku kodu związanego z procesorem oczekuje się operacji, która jest uruchamiana w wątku w tle za pomocą metody Task.Run.
Oczekiwane słowo kluczowe to miejsce magii. Daje kontrolę nad wywołującym metodę, która jest wykonywana w oczekiwaniu, i ostatecznie pozwala na reagowanie interfejsu użytkownika lub elastyczność usługi.
Przykład związany z We / Wy: Pobieranie danych z usługi internetowej
private readonly HttpClient _httpClient = new HttpClient();
downloadButton.Clicked += async (o, e) =>
{
// This line will yield control to the UI as the request
// from the web service is happening.
//
// The UI thread is now free to perform other work.
var stringData = await _httpClient.GetStringAsync(URL);
DoSomethingWithData(stringData);
};
Przykład związany z procesorem: Przeprowadzanie obliczeń dla gry
private DamageResult CalculateDamageDone()
{
// Code omitted:
//
// Does an expensive calculation and returns
// the result of that calculation.
}
calculateButton.Clicked += async (o, e) =>
{
// This line will yield control to the UI while CalculateDamageDone()
// performs its work. The UI thread is free to perform other work.
var damageResult = await Task.Run(() => CalculateDamageDone());
DisplayDamage(damageResult);
};
Przykłady powyżej pokazały, jak można używać asynchronizacji i oczekiwać na pracę związaną z We / Wy i procesor. Jest to klucz, który można zidentyfikować, gdy zadanie, które należy wykonać, jest związane z operacjami we / wy lub procesorem, ponieważ może to znacznie wpłynąć na wydajność kodu i potencjalnie może prowadzić do niewłaściwego użycia niektórych konstrukcji.
Oto dwa pytania, które powinieneś zadać przed napisaniem kodu:
Czy Twój kod będzie „czekał” na coś, na przykład dane z bazy danych?
- Jeśli twoja odpowiedź brzmi „tak”, to twoja praca jest związana z operacjami wejścia / wyjścia.
Czy Twój kod będzie wykonywał bardzo drogie obliczenia?
- Jeśli odpowiedziałeś „tak”, to twoja praca jest związana z procesorem.
Jeśli twoja praca jest związana z operacjami we / wy, użyj asynchronizacji i poczekaj bez Task.Run . Nie należy używać biblioteki zadań równoległych. Przyczynę tego opisano w artykule Async in Depth .
Jeśli twoja praca jest związana z procesorem i zależy ci na responsywności, użyj asynchronizacji i poczekaj, ale odrodzenie pracy w innym wątku za pomocą Task.Run. Jeśli praca jest odpowiednia dla współbieżności i równoległości, należy również rozważyć użycie biblioteki zadań równoległych .
Aplikacja jest związana z procesorem, gdy wydajność arytmetyczna / logiczna / zmiennoprzecinkowa (A / L / FP) podczas wykonywania jest najczęściej zbliżona do teoretycznej wydajności szczytowej procesora (dane dostarczone przez producenta i określone przez właściwości procesor: liczba rdzeni, częstotliwość, rejestry, ALU, FPU itp.).
Wydajność podglądu jest bardzo trudna do osiągnięcia w rzeczywistych aplikacjach, ponieważ nie jest to niemożliwe. Większość aplikacji uzyskuje dostęp do pamięci w różnych częściach wykonania, a procesor nie wykonuje operacji A / L / FP podczas kilku cykli. Nazywa się to ograniczeniem von Neumanna ze względu na odległość między pamięcią a procesorem.
Jeśli chcesz być blisko wydajności szczytowej procesora, strategią może być próba ponownego wykorzystania większości danych w pamięci podręcznej, aby uniknąć wymagania danych z pamięci głównej. Algorytmem wykorzystującym tę funkcję jest mnożenie macierzy i macierzy (jeśli obie macierze można zapisać w pamięci podręcznej). Dzieje się tak, ponieważ jeśli macierze mają rozmiar n x n
, należy wykonać 2 n^3
operacje wykorzystujące tylko 2 n^2
liczby danych FP. Z drugiej strony, dodawanie macierzy, na przykład, jest mniej związane z procesorem lub bardziej związane z pamięcią niż mnożenie macierzy, ponieważ wymaga tylko n^2
FLOP z tymi samymi danymi.
Na poniższym rysunku pokazano FLOP uzyskane naiwnymi algorytmami dodawania macierzy i mnożenia macierzy w procesorze Intel i5-9300H:
Należy zauważyć, że zgodnie z oczekiwaniami wydajność mnożenia macierzy jest większa niż dodawanie macierzy. Wyniki te można odtworzyć, uruchamiając test/gemm
i test/matadd
dostępne w tym repozytorium .
Sugeruję również obejrzenie filmu wideo o tym działaniu autorstwa J. Dongarry.
Proces związany z We / Wy: - Jeśli większość czasu życia procesu jest spędzana w stanie we / wy, wówczas proces jest związany z procesem we / wy. Przykład: -kalkulator, przeglądarka internetowa
Proces związany z procesorem: - Jeśli większość czasu życia procesu jest spędzana w jednostce centralnej, to jest to proces związany z jednostką centralną.