Gotowość a ukończenie Wykorzystanie pamięci asynchronicznej we / wy?

12

Oglądałem tę rozmowę o wdrażaniu Async IO w Rust, a Carl wymienia dwa potencjalne modele. Gotowość i realizacja.

Model gotowości:

  • informujesz jądro, że chcesz czytać z gniazda
  • rób inne rzeczy przez jakiś czas…
  • jądro informuje, kiedy gniazdo jest gotowe
  • czytasz (wypełniasz bufor)
  • rób co potrzebujesz
  • zwolnij bufor (dzieje się automatycznie z Rust)

Model wykonania:

  • alokujesz bufor dla jądra do wypełnienia
  • rób inne rzeczy przez jakiś czas…
  • jądro informuje, kiedy bufor został zapełniony
  • rób wszystko, co potrzebujesz z danymi
  • zwolnij bufor

W przykładzie Carla, w którym zastosowano model gotowości, można było iterować po wypełnieniu gotowych gniazd i zwolnieniu globalnego bufora, co wydaje się, że zużyłoby znacznie mniej pamięci.

Teraz moje założenia:

Pod maską (w przestrzeni jądra), gdy mówi się, że gniazdo jest „gotowe”, dane już istnieją. Wszedł do gniazda przez sieć (lub skądkolwiek), a system operacyjny przechowuje dane.

To nie tak, że alokacja pamięci magicznie nie występuje w modelu gotowości. Po prostu system operacyjny ją od ciebie odciąga. W modelu Completion system operacyjny prosi o przydzielenie pamięci, zanim dane faktycznie wpłyną, i oczywiste jest, co się dzieje.

Oto moja zmieniona wersja Modelu gotowości:

  • informujesz jądro, że chcesz czytać z gniazda
  • rób inne rzeczy przez jakiś czas…
  • POPRAWKA: dane przychodzą do systemu operacyjnego (jakieś miejsce w pamięci jądra)
  • jądro informuje, że gniazdo jest gotowe
  • czytasz (wypełniasz inny bufor oddzielnie od bufora jądra abover (lub dostajesz do niego wskaźnik?))
  • rób co potrzebujesz
  • zwolnij bufor (dzieje się automatycznie z Rust)

/ Moje założenia

Podoba mi się to, że program przestrzeni użytkownika jest niewielki, ale chciałem tylko wyjaśnić, co tak naprawdę dzieje się tutaj. Nie widzę, aby jeden model z natury zużywałby mniej pamięci lub obsługiwał wyższy poziom współbieżnych operacji we / wy. Bardzo chciałbym usłyszeć myśli i głębsze wyjaśnienie tego.

kjs3
źródło
Przyjechałem tu również z tego wykładu na YouTube. Dla każdego, kto uczy się, jak asynchroniczne IO lub sposobu realizacji pętli zdarzeń, zespół Rust ma tę playlistę „Wywiady Aysnc” tutaj wywiady z bardzo doświadczonych ludzi ze wspólnoty
cacoder

Odpowiedzi:

5

W modelu gotowości zużycie pamięci jest proporcjonalne do ilości danych nieużywanych przez aplikację.

W modelu ukończenia zużycie pamięci jest proporcjonalne do liczby zaległych wywołań gniazd.

Jeśli istnieje wiele gniazd, które są w większości bezczynne, model gotowości zużywa mniej pamięci.

Istnieje łatwa poprawka dla modelu ukończenia: zainicjuj odczyt 1 bajtu. To zużywa tylko niewielki bufor. Po zakończeniu odczytu wydaj kolejny (być może synchroniczny) odczyt, który pobierze resztę danych.

W niektórych językach model ukończenia jest niezwykle prosty do wdrożenia. Uważam to za dobry wybór domyślny.

usr
źródło
1

W modelu Completion system operacyjny prosi o przydzielenie pamięci, zanim dane faktycznie wpłyną, i oczywiste jest, co się dzieje.

Ale co się stanie, jeśli pojawi się więcej danych niż przydzielone miejsce? Jądro nadal musi przydzielić swój własny bufor, aby nie upuścić danych. (Na przykład dlatego działa 1-bajtowa sztuczka odczytu wspomniana w odpowiedzi usr.)

Kompromis polega na tym, że chociaż model ukończenia zużywa więcej pamięci, może także (czasami) wykonywać mniej operacji kopiowania, ponieważ utrzymywanie bufora w pobliżu oznacza, że ​​sprzęt może bezpośrednio DMA wyjść z niego lub do niego. Podejrzewam również (ale jestem mniej pewien), że Model Ukończenia wykonuje rzeczywistą operację kopiowania (jeśli istnieje) w innym wątku, przynajmniej dla IOCP Windows, podczas gdy Model Gotowości robi to jako część nieblokującego read()lub write()połączenie.

rpjohnst
źródło