Przeszukałem Internet pod kątem niektórych szczegółów technicznych dotyczących blokowania we / wy i nieblokujących operacji we / wy i znalazłem kilka osób, które twierdziły, że nieblokujące we / wy będą szybsze niż blokowanie we / wy. Na przykład w tym dokumencie .
Jeśli używam blokowania I / O, to oczywiście wątek, który jest aktualnie zablokowany, nie może zrobić nic innego ... Ponieważ jest zablokowany. Ale gdy tylko wątek zacznie być blokowany, system operacyjny może przełączyć się na inny wątek i nie przełączyć się z powrotem, dopóki nie zostanie coś do zrobienia dla zablokowanego wątku. Tak długo, jak istnieje inny wątek w systemie, który wymaga procesora i nie jest blokowany, nie powinno być więcej czasu bezczynności procesora w porównaniu z podejściem nieblokującym opartym na zdarzeniach, prawda?
Oprócz skrócenia czasu bezczynności procesora widzę jeszcze jedną opcję zwiększenia liczby zadań, które komputer może wykonać w danym przedziale czasowym: Zmniejszenie obciążenia związanego z przełączaniem wątków. Ale jak to zrobić? Czy narzut jest wystarczająco duży, aby pokazać wymierne efekty? Oto pomysł, jak mogę to sobie wyobrazić:
- Aby załadować zawartość pliku, aplikacja deleguje to zadanie do opartej na zdarzeniach struktury we / wy, przekazując funkcję wywołania zwrotnego wraz z nazwą pliku
- Struktura zdarzeń jest delegowana do systemu operacyjnego, który programuje kontroler DMA dysku twardego w celu zapisania pliku bezpośrednio w pamięci
- Struktura zdarzeń umożliwia uruchomienie dalszego kodu.
- Po zakończeniu kopiowania dysku do pamięci kontroler DMA powoduje przerwanie.
- Procedura obsługi przerwań systemu operacyjnego powiadamia opartą na zdarzeniach strukturę we / wy o całkowitym załadowaniu pliku do pamięci. Jak to się dzieje? Używając sygnału?
- Kod, który jest obecnie uruchamiany w ramach struktury we / wy zdarzenia, kończy się.
- Struktura we / wy oparta na zdarzeniach sprawdza swoją kolejkę i widzi komunikat systemu operacyjnego z kroku 5 i wykonuje wywołanie zwrotne otrzymane w kroku 1.
Czy tak to działa? Jeśli tak nie jest, jak to działa? Oznacza to, że system zdarzeń może działać bez konieczności jawnego dotykania stosu (na przykład prawdziwy harmonogram, który musiałby wykonać kopię zapasową stosu i skopiować stos innego wątku do pamięci podczas przełączania wątków)? Ile czasu to faktycznie oszczędza? Czy to coś więcej?
źródło
Odpowiedzi:
Największą zaletą nieblokujących lub asynchronicznych operacji we / wy jest to, że wątek może równolegle kontynuować swoją pracę. Oczywiście można to osiągnąć również za pomocą dodatkowego gwintu. Jak powiedziałeś, aby uzyskać najlepszą ogólną wydajność (systemu), wydaje mi się, że lepiej byłoby używać asynchronicznych operacji we / wy, a nie wielu wątków (co zmniejsza przełączanie wątków).
Przyjrzyjmy się możliwym implementacjom programu serwera sieciowego, który powinien obsłużyć 1000 klientów połączonych równolegle:
Każdy wątek wymaga zasobów pamięci (także pamięci jądra!), Co jest wadą. Każdy dodatkowy wątek oznacza więcej pracy dla planisty.
To powoduje obciążenie systemu, ponieważ mamy mniej wątków. Ale uniemożliwia to również wykorzystanie pełnej wydajności komputera, ponieważ możesz skończyć napędzając jeden procesor do 100% i pozwalając wszystkim innym procesorom pracować bezczynnie.
To powoduje obciążenie systemu, ponieważ jest mniej wątków. Może korzystać ze wszystkich dostępnych procesorów. W systemie Windows to podejście jest obsługiwane przez interfejs API puli wątków .
Oczywiście posiadanie większej liczby wątków samo w sobie nie stanowi problemu. Jak pewnie zauważyłeś, wybrałem dość dużą liczbę połączeń / wątków. Wątpię, czy zauważysz jakąkolwiek różnicę między trzema możliwymi implementacjami, jeśli mówimy o zaledwie kilkunastu wątkach (to również sugeruje Raymond Chen w poście na blogu MSDN Czy system Windows ma limit 2000 wątków na proces? ).
W systemie Windows użycie niebuforowanego wejścia / wyjścia pliku oznacza, że wielkość zapisów musi być wielokrotnością rozmiaru strony. Nie testowałem tego, ale wygląda na to, że może to również wpłynąć pozytywnie na wydajność zapisu dla buforowanych zapisów synchronicznych i asynchronicznych.
Kroki od 1 do 7, które opisujesz, dają dobre wyobrażenie o tym, jak to działa. W systemie Windows system operacyjny poinformuje Cię o zakończeniu asynchronicznego I / O (
WriteFile
zeOVERLAPPED
strukturą) za pomocą zdarzenia lub wywołania zwrotnego. Funkcje oddzwaniania będą wywoływane tylko na przykład wtedy, gdy Twój kod wywołaWaitForMultipleObjectsEx
zbAlertable
ustawioną natrue
.Więcej lektur w sieci:
źródło
We / wy obejmuje wiele rodzajów operacji, takich jak odczytywanie i zapisywanie danych z dysków twardych, uzyskiwanie dostępu do zasobów sieciowych, wywoływanie usług internetowych lub pobieranie danych z baz danych. W zależności od platformy i rodzaju operacji, asynchroniczne operacje we / wy zwykle korzystają z dowolnego sprzętu lub niskopoziomowego wsparcia systemowego do wykonywania operacji. Oznacza to, że będzie to wykonywane z jak najmniejszym wpływem na procesor.
Na poziomie aplikacji asynchroniczne operacje we / wy uniemożliwiają wątkom czekanie na zakończenie operacji we / wy. Zaraz po uruchomieniu asynchronicznej operacji we / wy zwalnia wątek, w którym została uruchomiona, i rejestrowane jest wywołanie zwrotne. Po zakończeniu operacji wywołanie zwrotne jest umieszczane w kolejce do wykonania w pierwszym dostępnym wątku.
Jeśli operacja we / wy jest wykonywana synchronicznie, jej działający wątek nie robi nic do momentu zakończenia operacji. Środowisko wykonawcze nie wie, kiedy zakończy się operacja we / wy, więc będzie okresowo dostarczać trochę czasu procesora do oczekującego wątku, czyli czasu procesora, który w przeciwnym razie mógłby zostać wykorzystany przez inne wątki, które mają do wykonania rzeczywiste operacje związane z procesorem.
Tak więc, jak wspomniał @ user1629468, asynchroniczne operacje we / wy nie zapewniają lepszej wydajności, ale raczej lepszą skalowalność. Jest to oczywiste, gdy działa w kontekstach, które mają ograniczoną liczbę dostępnych wątków, tak jak ma to miejsce w przypadku aplikacji internetowych. Aplikacje internetowe zwykle używają puli wątków, z której przypisują wątki do każdego żądania. Jeśli żądania są blokowane podczas długotrwałych operacji we / wy, istnieje ryzyko wyczerpania puli internetowej i spowodowania zawieszenia lub spowolnienia reakcji aplikacji internetowej.
Jedną z rzeczy, które zauważyłem, jest to, że asynchroniczne operacje we / wy nie są najlepszą opcją w przypadku bardzo szybkich operacji we / wy. W takim przypadku korzyść z nie utrzymywania zajętości wątku podczas oczekiwania na zakończenie operacji we / wy nie jest bardzo ważna, a fakt, że operacja jest uruchamiana w jednym wątku, a kończy się w innym, zwiększa ogólne wykonanie.
Możesz przeczytać bardziej szczegółowe badania, które ostatnio przeprowadziłem na temat asynchronicznych operacji we / wy i wielowątkowości tutaj .
źródło
Głównym powodem korzystania z AIO jest skalowalność. Z perspektywy kilku wątków korzyści nie są oczywiste. Ale gdy system skaluje się do 1000 wątków, AIO zapewni znacznie lepszą wydajność. Zastrzeżenie jest takie, że biblioteka AIO nie powinna wprowadzać dalszych wąskich gardeł.
źródło
Aby założyć poprawę szybkości ze względu na jakąkolwiek formę przetwarzania wielu komputerów, należy założyć, że wiele zadań opartych na procesorach jest wykonywanych jednocześnie na wielu zasobach obliczeniowych (zazwyczaj rdzeniach procesora) lub że nie wszystkie zadania zależą od równoczesnego użycia ten sam zasób - to znaczy, że niektóre zadania mogą zależeć od jednego podkomponentu systemu (powiedzmy pamięć dyskowa), podczas gdy niektóre zadania zależą od innego (odbieranie komunikacji z urządzenia peryferyjnego), a jeszcze inne mogą wymagać użycia rdzeni procesora.
Pierwszy scenariusz jest często nazywany programowaniem „równoległym”. Drugi scenariusz jest często określany jako programowanie „współbieżne” lub „asynchroniczne”, chociaż termin „współbieżny” jest czasami używany również w odniesieniu do przypadku, gdy po prostu zezwala się systemowi operacyjnemu na przeplatanie wykonywania wielu zadań, niezależnie od tego, czy takie wykonanie musi zająć umieszczać szeregowo lub jeśli można użyć wielu zasobów, aby uzyskać wykonywanie równoległe. W tym drugim przypadku określenie „współbieżne” odnosi się ogólnie do sposobu, w jaki wykonanie jest zapisywane w programie, a nie z perspektywy faktycznej jednoczesności wykonywania zadań.
Bardzo łatwo o tym wszystkim mówić z milczącymi założeniami. Na przykład niektórzy szybko zgłaszają oświadczenie, takie jak „Asynchroniczne operacje we / wy będą szybsze niż wielowątkowe we / wy”. To twierdzenie jest wątpliwe z kilku powodów. Po pierwsze, może się zdarzyć, że pewna podana asynchroniczna struktura we / wy jest zaimplementowana dokładnie z wielowątkowością, w którym to przypadku są one tym samym i nie ma sensu mówić, że jedna koncepcja „jest szybsza” niż druga .
Po drugie, nawet w przypadku, gdy istnieje jednowątkowa implementacja struktury asynchronicznej (np. Jednowątkowa pętla zdarzeń), nadal należy przyjąć założenie dotyczące tego, co robi ta pętla. Na przykład jedną głupią rzeczą, jaką można zrobić z jednowątkową pętlą zdarzeń, jest żądanie asynchronicznego wykonania dwóch różnych zadań związanych wyłącznie z procesorem. Jeśli zrobiłeś to na maszynie z wyidealizowanym rdzeniem pojedynczego procesora (ignorując współczesne optymalizacje sprzętowe), wykonanie tego zadania „asynchronicznie” nie byłoby tak naprawdę inne niż wykonanie go z dwoma niezależnie zarządzanymi wątkami lub tylko z jednym samotnym procesem - - różnica może sprowadzać się do przełączania kontekstu wątków lub optymalizacji harmonogramu systemu operacyjnego, ale jeśli oba zadania są kierowane do procesora, w obu przypadkach byłoby podobnie.
Warto wyobrazić sobie wiele nietypowych lub głupich przypadków narożnych, na które możesz się natknąć.
„Asynchroniczny” nie musi być współbieżny, na przykład tak jak powyżej: „asynchronicznie” wykonujesz dwa zadania związane z procesorem na maszynie z dokładnie jednym rdzeniem procesora.
Wykonywanie wielowątkowe nie musi być współbieżne: tworzysz dwa wątki na maszynie z jednym rdzeniem procesora lub prosisz dwa wątki o pozyskanie innego rodzaju rzadkiego zasobu (wyobraź sobie, na przykład, sieciową bazę danych, która może ustanowić tylko jeden połączenie na raz). Wykonywanie wątków może być przeplatane, jednak harmonogram systemu operacyjnego uzna to za stosowne, ale ich całkowity czas wykonywania nie może zostać zmniejszony (i zostanie zwiększony z przełączania kontekstu wątku) na jednym rdzeniu (lub bardziej ogólnie, jeśli spawnujesz więcej wątków niż jest) rdzeni, aby je uruchomić lub mieć więcej wątków proszących o zasób, niż to, co zasób może utrzymać). To samo dotyczy również przetwarzania wielokrotnego.
Zatem ani asynchroniczne operacje we / wy, ani wielowątkowość nie muszą oferować żadnego wzrostu wydajności pod względem czasu wykonywania. Mogą nawet spowolnić działanie.
Jeśli jednak zdefiniujesz konkretny przypadek użycia, taki jak określony program, który zarówno wykonuje wywołanie sieciowe w celu pobrania danych z zasobu podłączonego do sieci, takiego jak zdalna baza danych, a także wykonuje pewne lokalne obliczenia związane z procesorem, możesz zacząć rozważać różnice w wydajności między tymi dwiema metodami przy określonym założeniu dotyczącym sprzętu.
Pytania, które należy zadać: Ile kroków obliczeniowych muszę wykonać i ile jest niezależnych systemów zasobów, aby je wykonać? Czy istnieją podzbiory kroków obliczeniowych, które wymagają użycia niezależnych podskładników systemu i mogą przynosić korzyści z robienia tego jednocześnie? Ile mam rdzeni procesorów i jakie są narzuty związane z używaniem wielu procesorów lub wątków do wykonywania zadań na oddzielnych rdzeniach?
Jeśli Twoje zadania w dużej mierze opierają się na niezależnych podsystemach, rozwiązanie asynchroniczne może być dobre. Jeśli liczba wątków potrzebnych do jego obsługi byłaby duża, tak że przełączanie kontekstu stało się nietrywialne dla systemu operacyjnego, wówczas rozwiązanie asynchroniczne jednowątkowe mogłoby być lepsze.
Ilekroć zadania są powiązane przez ten sam zasób (np. Wiele potrzeb, aby jednocześnie uzyskać dostęp do tej samej sieci lub zasobu lokalnego), wówczas wielowątkowość prawdopodobnie wprowadzi niezadowalający narzut, a podczas gdy asynchronia jednowątkowa może wprowadzić mniejszy narzut, w takim zasobie ograniczona sytuacja to również nie może spowodować przyspieszenia. W takim przypadku jedyną opcją (jeśli chcesz przyspieszyć) jest udostępnienie wielu kopii tego zasobu (np. Wielu rdzeni procesora, jeśli rzadkim zasobem jest procesor; lepsza baza danych, która obsługuje więcej równoczesnych połączeń, jeśli rzadki zasób to baza danych z ograniczeniem połączeń itp.).
Można to ująć w inny sposób: zezwolenie systemowi operacyjnemu na przeplatanie wykorzystania jednego zasobu do dwóch zadań nie może być szybsze niż zwykłe pozwolenie jednemu zadaniu na wykorzystanie zasobu, podczas gdy drugie czeka, a następnie pozwolenie drugiemu zadaniu na kolejne zakończenie. Co więcej, koszt planistycznego przeplatania oznacza, że w każdej rzeczywistej sytuacji faktycznie powoduje spowolnienie. Nie ma znaczenia, czy użycie z przeplotem dotyczy procesora, zasobu sieciowego, zasobu pamięci, urządzenia peryferyjnego czy jakiegokolwiek innego zasobu systemowego.
źródło
Jedną z możliwych implementacji nieblokującego wejścia / wyjścia jest dokładnie to, co powiedziałeś, z pulą wątków działających w tle, które blokują wejścia / wyjścia i powiadamiają wątek inicjatora wejścia / wyjścia za pośrednictwem mechanizmu wywołania zwrotnego. W rzeczywistości tak działa moduł AIO w glibc. Oto kilka niejasnych szczegółów dotyczących implementacji.
Chociaż jest to dobre rozwiązanie, które jest dość przenośne (o ile masz wątki), system operacyjny zazwyczaj jest w stanie wydajniej obsługiwać nieblokujące operacje we / wy. W tym artykule w Wikipedii wymieniono możliwe implementacje poza pulą wątków.
źródło
Obecnie jestem w trakcie wdrażania async io na platformie embedded przy użyciu protothreads. Nieblokujący io decyduje o różnicy między pracą z prędkością 16000 kl./s a 160 kl./s. Największą zaletą nieblokującego io jest to, że możesz tak zorganizować swój kod, aby robić inne rzeczy, podczas gdy sprzęt robi swoje. Nawet inicjalizację urządzeń można przeprowadzić równolegle.
Jaskółka oknówka
źródło
W Node uruchamianych jest wiele wątków, ale jest to warstwa w dół w czasie wykonywania C ++.
https://codeburst.io/how-node-js-single-thread-mechanism-work-understanding-event-loop-in-nodejs-230f7440b0ea
https://itnext.io/multi-threading-and-multi-process-in-node-js-ffa5bb5cde98
Wyjaśnienie „Węzeł jest szybszy, ponieważ nie blokuje…” jest trochę marketingowe i to jest świetne pytanie. Jest wydajna i skalowalna, ale nie jest dokładnie jednowątkowa.
źródło
O ile wiem, poprawa polega na tym, że asynchroniczne we / wy wykorzystuje (mówię o MS System, tylko dla wyjaśnienia) tak zwane porty zakończenia we / wy . Korzystając z wywołania asynchronicznego, platforma automatycznie wykorzystuje taką architekturę, co ma być znacznie bardziej wydajne niż standardowy mechanizm wątkowania. Z własnego doświadczenia mogę powiedzieć, że rozsądnie czułbyś, że twoja aplikacja jest bardziej reaktywna, jeśli wolisz AsyncCalls zamiast blokowania wątków.
źródło
Pozwólcie, że podam wam kontrprzykład, że asynchroniczne we / wy nie działa. Piszę proxy podobne do poniższego przy użyciu boost :: asio. https://github.com/ArashPartow/proxy/blob/master/tcpproxy_server.cpp
Jednak scenariusz mojego przypadku jest taki, że wiadomości przychodzące (od strony klienta) są szybkie, podczas gdy wychodzące (po stronie serwera) są powolne dla jednej sesji, aby nadążyć za prędkością przychodzącą lub zmaksymalizować całkowitą przepustowość proxy, musimy użyć wiele sesji w ramach jednego połączenia.
Tak więc ta asynchroniczna struktura we / wy już nie działa. Potrzebujemy puli wątków do wysłania na serwer, przypisując każdemu wątkowi sesję.
źródło