Ostrzeżenia dotyczące reaktorów typu select / poll w porównaniu z reaktorami epoll w Twisted

95

Wszystko, co przeczytałem i doświadczyłem (aplikacje oparte na Tornado) prowadzi mnie do przekonania, że ​​ePoll jest naturalnym zamiennikiem sieci opartej na Select i Poll, zwłaszcza w przypadku Twisted. Co sprawia, że ​​mam paranoję, rzadko kiedy lepsza technika lub metodologia nie ma ceny.

Przeczytanie kilkudziesięciu porównań między epollem a alternatywami pokazuje, że epoll jest bez wątpienia mistrzem szybkości i skalowalności, zwłaszcza że skaluje się liniowo, co jest fantastyczne. To powiedziawszy, a co z wykorzystaniem procesora i pamięci, czy epoll nadal jest mistrzem?

David
źródło

Odpowiedzi:

191

W przypadku bardzo małej liczby gniazd (różni się oczywiście w zależności od sprzętu, ale mówimy o czymś, co jest rzędu 10 lub mniej), funkcja select może pokonać epoll w zużyciu pamięci i szybkości działania. Oczywiście przy tak małej liczbie gniazd oba mechanizmy są tak szybkie, że w zdecydowanej większości przypadków nie przejmujesz się tą różnicą.

Jedno wyjaśnienie. Zarówno wybierz, jak i epoll skalują liniowo. Dużą różnicą jest jednak to, że interfejsy API skierowane do przestrzeni użytkownika mają złożoność opartą na różnych rzeczach. Koszt selectpołączenia jest mniej więcej równy wartości podanego deskryptora pliku o najwyższym numerze. Jeśli wybierzesz na jednym fd, 100, to jest to mniej więcej dwa razy droższe niż wybranie na jednym fd, 50. Dodanie większej liczby fd poniżej najwyższego nie jest całkiem darmowe, więc jest to trochę bardziej skomplikowane niż to w praktyce, ale to jest dobrym pierwszym przybliżeniem dla większości implementacji.

Koszt epoll jest bliższy liczbie deskryptorów plików, które faktycznie zawierają zdarzenia. Jeśli monitorujesz 200 deskryptorów plików, ale tylko 100 z nich ma zdarzenia, wtedy (z grubsza) płacisz tylko za te 100 aktywnych deskryptorów plików. W tym przypadku epoll ma zwykle jedną ze swoich głównych zalet w stosunku do Select. Jeśli masz tysiąc klientów, którzy są w większości bezczynni, to kiedy używasz select, nadal płacisz za wszystkie tysiąc z nich. Jednak w przypadku epoll wygląda to tak, jakbyś miał tylko kilka - płacisz tylko za te, które są aktywne w danym momencie.

Wszystko to oznacza, że ​​epoll spowoduje mniejsze wykorzystanie procesora w przypadku większości obciążeń. Jeśli chodzi o użycie pamięci, to trochę rzutowanie. selectudaje się przedstawić wszystkie niezbędne informacje w bardzo zwarty sposób (jeden bit na deskryptor pliku). A ograniczenie FD_SETSIZE (zwykle 1024) dotyczące liczby deskryptorów plików, których możesz użyć, selectoznacza, że ​​nigdy nie wydasz więcej niż 128 bajtów na każdy z trzech zestawów fd, których możesz użyćselect(czytaj, pisz, wyjątek). W porównaniu do tych maksymalnie 384 bajtów, epoll jest czymś w rodzaju świni. Każdy deskryptor pliku jest reprezentowany przez strukturę wielobajtową. Jednak w kategoriach bezwzględnych nadal nie będzie zużywał dużo pamięci. Możesz przedstawić ogromną liczbę deskryptorów plików w kilkudziesięciu kilobajtach (myślę, że około 20k na 1000 deskryptorów plików). Możesz także dorzucić fakt, że musisz wydać wszystkie 384 z tych bajtów, selectjeśli chcesz monitorować tylko jeden deskryptor pliku, ale jego wartość wynosi 1024, podczas gdy w przypadku epoll wydasz tylko 20 bajtów. Mimo to wszystkie te liczby są dość małe, więc nie ma to większego znaczenia.

Jest też inna zaleta epoll, o której być może już wiesz, że nie ogranicza się ona do deskryptorów plików FD_SETSIZE. Możesz go użyć do monitorowania dowolnej liczby deskryptorów plików. A jeśli masz tylko jeden deskryptor pliku, ale jego wartość jest większa niż FD_SETSIZE, epoll też z tym działa, ale selecttak nie jest.

Losowo niedawno odkryłem również jedną drobną wadę epollw porównaniu z selectlub poll. Chociaż żaden z tych trzech interfejsów API nie obsługuje zwykłych plików (tj. Plików w systemie plików), selecti pollprzedstawia ten brak wsparcia jako zgłaszanie takich deskryptorów jako zawsze czytelnych i zawsze zapisywalnych. To sprawia, że ​​nie nadają się do żadnego znaczącego rodzaju nieblokującego wejścia / wyjścia systemu plików, program, który używa selectlub polli zdarza się napotkać deskryptor pliku z systemu plików, będzie przynajmniej kontynuował działanie (lub jeśli się nie powiedzie, nie będzie to spowodowane z selectlub poll), choć być może nie o najlepszej wydajności.

Z drugiej strony epollzawiedzie szybko z błędem ( EPERMnajwyraźniej), gdy zostanie wyświetlony monit o monitorowanie takiego deskryptora pliku. Ściśle mówiąc, nie jest to błędne. Po prostu sygnalizuje brak wsparcia w wyraźny sposób. Normalnie biłbym brawa dla jawnych warunków awarii, ale ten jest nieudokumentowany (o ile wiem) i skutkuje całkowicie zepsutą aplikacją, a nie taką, która działa tylko z potencjalnie obniżoną wydajnością.

W praktyce jedyne miejsce, w którym widziałem, to podczas interakcji ze stdio. Użytkownik może przekierować stdin lub stdout z / do normalnego pliku. Podczas gdy wcześniej stdin i stdout byłyby potokiem - obsługiwanym przez epoll w porządku - wtedy staje się normalnym plikiem i epoll głośno kończy się niepowodzeniem, co powoduje uszkodzenie aplikacji.

Jean-Paul Calderone
źródło
Bardzo ładna odpowiedź. Rozważ wyraźne określenie zachowania w pollcelu uzyskania kompletności?
kwark
6
Moje dwa centy za zachowanie czytania ze zwykłych plików: generalnie wolę jawną awarię od pogorszenia wydajności. Powodem jest to, że jest znacznie bardziej prawdopodobne, że zostanie wykryty podczas programowania, a zatem obejdzie się poprawnie (powiedzmy, mając alternatywną metodę wykonywania operacji we / wy dla rzeczywistych plików). YMMV oczywiście: może nie być zauważalnego spowolnienia, w którym to przypadku awaria nie jest lepsza. Ale dramatyczne spowolnienie, które ma miejsce tylko w specjalnych przypadkach, może być bardzo trudne do złapania podczas opracowywania, pozostawiając je jako bombę zegarową, gdy zostanie faktycznie zastosowana.
kwark
1
Muszę tylko przeczytać całą edycję. W pewnym sensie zgadzam się, że prawdopodobnie nie jest właściwe, aby epoll nie naśladował swoich poprzedników, ale z drugiej strony mogę sobie wyobrazić programistę, który zaimplementował błąd EPERM, pomyślał: dobrze." I jeszcze jeden kontrargument, jestem programistą defensywnym, cokolwiek powyżej 1 + 1 jest podejrzane i koduję w taki sposób, aby pozwolić na pełne gracji niepowodzenia. Wystrzelenie jądra i błąd niezgodny z oczekiwaniami nie jest przyjemny ani rozważny.
David
1
@ Jean-Paul czy mógłbyś również dodać wyjaśnienie dotyczące kqueue?
Good Person,
Pomijając wydajność, czy jest man selectjakiś problem wynikający z tego (z ) Jądro Linuksa nie narzuca ustalonego limitu, ale implementacja glibc sprawia, że ​​fd_set jest typem o stałym rozmiarze, z FD_SETSIZE zdefiniowanym jako 1024, a makra FD _ * () działające zgodnie z ten limit. Aby monitorować deskryptory plików większe niż 1023, użyj zamiast tego poll (2). W CentOS 7 widziałem już problemy, w których mój własny kod nie powiódł się z funkcją select (), ponieważ jądro zwróciło uchwyt pliku> 1023, a obecnie patrzę na problem, który pachnie, jakby to był Twisted, który uderza w ten sam problem.
Paul D Smith
4

Podczas testów w mojej firmie pojawił się jeden problem z epoll (), a więc jeden koszt w porównaniu z wyborem.

Podczas próby odczytu z sieci z przekroczeniem limitu czasu, utworzenie epoll_fd (zamiast FD_SET) i dodanie fd do epoll_fd jest znacznie droższe niż utworzenie FD_SET (który jest prostym malloc).

Zgodnie z poprzednią odpowiedzią, gdy liczba FD w procesie staje się duża, koszt funkcji select () staje się wyższy, ale w naszych testach, nawet przy wartościach fd rzędu 10000, select wciąż był zwycięzcą. Są to przypadki, w których istnieje tylko jeden plik FD, na który czeka wątek, i po prostu próbuje się przezwyciężyć fakt, że odczyt sieciowy i zapis sieciowy nie przekraczają limitu czasu podczas korzystania z modelu wątku blokującego. Oczywiście modele wątków blokujących mają niską wydajność w porównaniu z nieblokującymi systemami reaktorów, ale zdarzają się sytuacje, w których integracja z konkretną starszą bazą kodu jest wymagana.

Ten rodzaj przypadku użycia jest rzadki w aplikacjach o wysokiej wydajności, ponieważ model reaktora nie musi za każdym razem tworzyć nowego epoll_fd. W przypadku modelu, w którym epoll_fd ma długą żywotność - co jest zdecydowanie preferowane w przypadku każdego projektu serwera o wysokiej wydajności - epoll jest zdecydowanym zwycięzcą pod każdym względem.

Brian Bulkowski
źródło
5
Ale nie możesz nawet użyć, select()jeśli masz wartości deskryptorów plików w zakresie 10k + - chyba że przekompilujesz połowę systemu, aby zmienić FD_SETSIZE - więc zastanawiam się, jak ta strategia w ogóle działała. W przypadku scenariusza, który opisałeś, prawdopodobnie przyjrzałbym się, poll()który jest bardziej podobny select()niż podobny epoll()- ale usuwa ograniczenie FD_SETSIZE.
Jean-Paul Calderone
Możesz użyć select (), jeśli masz wartości deskryptorów plików z zakresu 10K, ponieważ możesz malloc () i FD_SET. W rzeczywistości, ponieważ FD_SETSIZE to czas kompilacji, a rzeczywisty limit fd jest w czasie wykonywania, JEDYNE bezpieczne użycie FD_SET sprawdza numer deskryptora pliku względem rozmiaru FD_SET i wykonuje malloc (lub moralny odpowiednik), jeśli FD_SET jest za mały. Byłem zszokowany, gdy zobaczyłem to w produkcji z klientem. Po 20 latach programowania gniazd, cały kod, który kiedykolwiek napisałem - i większość tutoriali w sieci - są niebezpieczne.
Brian Bulkowski
5
O ile wiem, nie jest to prawdą na żadnych popularnych platformach. FD_SETSIZEjest stałą czasową kompilacji ustawianą podczas kompilacji biblioteki C. Jeśli zdefiniujesz ją na inną wartość podczas budowania aplikacji, twoja aplikacja i biblioteka C nie będą się zgadzać i wszystko pójdzie źle. Jeśli masz referencje, które twierdzą, że można bezpiecznie przedefiniować FD_SETSIZE, chciałbym je zobaczyć.
Jean-Paul Calderone