2008-09-27 13 views
7

Wygląda na to, że istnieje kilka opcji dostępnych dla programów obsługujących dużą liczbę połączeń z gniazdami (takich jak usługi sieciowe, systemy p2p itp.).Jak najlepiej radzić sobie z dużą liczbą deskryptorów plików?

  1. Nawiązać osobny wątek do obsługi wejść/wyjść dla każdego gniazda.
  2. Użyj wywołania systemowego select, aby zmultipleksować wejścia/wyjścia w pojedynczy wątek.
  3. Użyj wywołania systemowego poll, aby zmultipleksować wejścia/wyjścia (zastępując wybór).
  4. Użyj wywołań systemowych epoll, aby uniknąć konieczności wielokrotnego wysyłania gniazd fd przez granice użytkownika/systemu.
  5. Zajarzenie liczby wątków we/wy, które każdy multipleksuje stosunkowo mały zestaw całkowitej liczby połączeń za pomocą interfejsu API ankiety.
  6. Zgodnie z # 5, za wyjątkiem korzystania z epoll API do tworzenia oddzielnego obiektu epoll dla każdego niezależnego wątku we/wy.

W procesorze wielordzeniowym spodziewałbym się, że najlepsze wyniki będą miały numery 5 lub 6, ale nie mam żadnych twardych kopii zapasowych danych. Przeszukując stronę internetową, pojawiła się strona opisująca doświadczenia autora testującego podejścia # 2, # 3 i # 4 powyżej. Niestety strona ta ma około 7 lat i nie można znaleźć żadnych oczywistych najnowszych aktualizacji.

Moje pytanie brzmi, które z tych podejść okazały się najbardziej skuteczne i czy istnieje inne podejście, które działa lepiej niż którekolwiek z wymienionych powyżej? Należy docenić odwołania do rzeczywistych wykresów, oficjalnych publikacji i/lub internetowych publikacji.

+0

Myślę, że jest to rozwiązany problem, a odpowiedź jest tutaj - http://www.kegel.com/c10k.html – computinglife

Odpowiedz

0

Używam intensywnie epoll() i działa dobrze. Rutynowo mam tysiące gniazd aktywnych i testuję z 131,072 gniazdami. I epoll() zawsze może sobie z tym poradzić.

Używam wielu wątków, z których każdy odpytuje na podzbiorze gniazd. To komplikuje kod, ale w pełni wykorzystuje wielordzeniowe procesory.

2

Z mojego doświadczenia wynika, że ​​najlepsze wyniki uzyskasz dzięki # 6.

Polecam również zajrzeć do libevent, aby poradzić sobie z abstrahowaniem niektórych z tych szczegółów. Przynajmniej będziesz mógł zobaczyć ich benchmark benchmark results.

Co więcej, o ilu gniazdach mówisz? Twoje podejście prawdopodobnie nie ma znaczenia, dopóki nie zaczniesz zdobywać co najmniej kilkuset gniazd.

3

Mówiąc z mojego doświadczenia z uruchomieniem dużych serwerów IRC, użyliśmy select() i poll() (ponieważ epoll()/kqueue() nie były dostępne). Przy około 700 równoczesnych klientach serwer używałby 100% CPU (serwer IRC nie był wielowątkowy). Jednak, co ciekawe, serwer nadal będzie działał dobrze. Na około 4 000 klientów serwer zaczął się opóźniać.

Powodem tego było, że na około 700 klientów, kiedy wrócimy do select(), będzie jeden klient dostępny do przetworzenia. Skanowanie pętli for() w celu sprawdzenia, który to klient, pochłania większość procesora. Ponieważ mamy więcej klientów, zaczęlibyśmy dostawać coraz więcej klientów wymagających przetwarzania w każdym wywołaniu funkcji select(), abyśmy byli bardziej wydajni.

Przeprowadzka do epoll()/kqueue(), podobne maszyny mogłyby trywialnie poradzić sobie z 10 000 klientów, z niektórymi (bez wątpienia mocniejsze maszyny, ale wciąż maszyny, które byłyby uważane za małe według dzisiejszych standardów), utrzymywały 30 000 klienci bez przerywania potu.

Eksperymenty, które widziałem z SIGIO, sugerują, że działa dobrze w aplikacjach, w których opóźnienie jest niezwykle ważne, gdy tylko niewielu aktywnych klientów wykonuje bardzo niewiele pracy indywidualnej.

Polecam używanie epoll()/kqueue() nad select()/poll() w prawie każdej sytuacji. Nie eksperymentowałem z dzieleniem klientów między wątkami. Szczerze mówiąc, nigdy nie znalazłem usługi wymagającej więcej pracy optymalizacyjnej na frontowym przetwarzaniu klienta, aby usprawiedliwić eksperymentowanie z wątkami.

2

Spędziłem ostatnie dwa lata pracując nad tym konkretnym zagadnieniem (dla serwera WWW G-WAN, który zawiera WIELU benchmarków i wykresów odsłaniających to wszystko).

Model najlepiej działający pod Linuksem to epol z jedną kolejką zdarzeń (oraz, w przypadku dużego przetwarzania, kilka wątków roboczych).

Jeśli masz mało przetwarzania (niskie opóźnienie przetwarzania), użycie jednego wątku będzie szybsze przy użyciu kilku wątków.

Powodem tego jest to, że epoll nie skaluje się na procesorach wielordzeniowych (użycie kilku współbieżnych kolejek epilacji dla połączeń we/wy w tej samej aplikacji trybu użytkownika spowoduje jedynie spowolnienie działania serwera).

Nie patrzyłem poważnie na kod epoll w jądrze (do tej pory skupiałem się tylko na trybie użytkownika), ale domyślam się, że implementacja epoli w kernelu jest ułomna przez blokady.

Dlatego użycie kilku gwintów szybko uderza w ścianę.

Jest rzeczą oczywistą, że taki zły stan rzeczy nie powinien trwać, jeśli Linux chce utrzymać swoją pozycję jako jeden z najlepiej działających jądra.

Powiązane problemy