Jak libuv wypada w porównaniu do Boost / ASIO?


239

Byłbym zainteresowany takimi aspektami jak:

  • zakres / funkcje
  • występ
  • dojrzałość

20
Wróćmy do tego pytania i uzyskaj dobre odpowiedzi!
Wietnam

\ o / .. mam nadzieję, że otrzymamy wnikliwe odpowiedzi!
oberstet

Odpowiedzi:


493

Zakres

Boost.Asio to biblioteka C ++, która zaczęła się od sieci, ale jej asynchroniczne funkcje we / wy zostały rozszerzone na inne zasoby. Dodatkowo, ponieważ Boost.Asio jest częścią bibliotek Boost, jego zakres jest nieco zawężony, aby zapobiec duplikacji z innymi bibliotekami Boost. Na przykład Boost.Asio nie zapewni abstrakcji nici, ponieważ Boost.Thread już ją udostępnia.

Z drugiej strony, libuv jest biblioteką C zaprojektowany jako warstwa platforma node.js . Zapewnia abstrakcję dla IOCP w systemie Windows, kqueue na macOS i epoll w systemie Linux. Dodatkowo wygląda na to, że jego zakres nieznacznie się zwiększył, włączając abstrakcje i funkcje, takie jak wątki, pule wątków i komunikacja między wątkami.

U podstaw każdej biblioteki znajduje się pętla zdarzeń i asynchroniczne funkcje we / wy. Nakładają się na siebie niektóre podstawowe funkcje, takie jak liczniki czasu, gniazda i operacje asynchroniczne. libuv ma szerszy zakres i zapewnia dodatkowe funkcje, takie jak abstrakcje wątków i synchronizacji, synchroniczne i asynchroniczne operacje na systemie plików, zarządzanie procesami itp. W przeciwieństwie do tego, pierwotne ukierunkowanie sieciowe Boost.Asio, ponieważ zapewnia bogatszy zestaw powiązanych z siecią możliwości, takie jak ICMP, SSL, synchroniczne operacje blokowania i nieblokowania oraz operacje wyższego poziomu do typowych zadań, w tym odczytywanie ze strumienia do momentu otrzymania nowej linii.


Lista funkcji

Oto krótkie porównanie niektórych głównych funkcji. Ponieważ programiści używający Boost.Asio często dysponują innymi bibliotekami Boost, zdecydowałem się rozważyć dodatkowe biblioteki Boost, jeśli są one dostarczane bezpośrednio lub są trywialne do wdrożenia.

                         libuv Boost
Pętla zdarzeń: tak Asio
Pula wątków: tak Asio + wątki
Gwintowanie:              
  Wątki: tak Wątki
  Synchronizacja: tak Wątki
Operacje systemu plików:
  Synchroniczny: tak FileSystem
  Asynchroniczny: tak System plików Asio +
Timery: tak Asio
Scatter / Gather I / O [1] : no Asio
Sieć:
  ICMP: brak Asio
  Rozdzielczość DNS: Asio tylko Async
  SSL: bez Asio
  TCP: Asio-only asio
  UDP: Asio-only asio
Sygnał:
  Obsługa: tak Asio
  Wysyłanie: tak nie
IPC:
  Gniazda domen UNIX: tak Asio
  Nazwana rura systemu Windows: tak Asio
Zarządzanie procesem:
  Odłączanie: tak Proces
  Rura I / O: tak Proces
  Tarło: tak Proces
Zapytania systemowe:
  CPU: tak nie
  Interfejs sieciowy: tak nie
Porty szeregowe: nie tak
TTY: tak nie
Ładowanie biblioteki współdzielonej: tak Rozszerzenie [2]

1. rozproszony / Zebrać I / O .

2. Zwiększenie . Rozszerzenie nigdy nie zostało przesłane do oceny w ramach wzmocnienia. Jak wspomniano tutaj , autor uważa to za kompletne.

Pętla zdarzeń

Chociaż zarówno libuv, jak i Boost.Asio zapewniają pętle zdarzeń, istnieją między nimi pewne subtelne różnice:

  • Chociaż libuv obsługuje wiele pętli zdarzeń, nie obsługuje uruchamiania tej samej pętli z wielu wątków. Z tego powodu należy zachować ostrożność, używając domyślnej pętli ( uv_default_loop()), zamiast tworzyć nową pętlę ( uv_loop_new()), ponieważ inny komponent może uruchamiać domyślną pętlę.
  • Boost.Asio nie ma pojęcia domyślnej pętli; wszystkie io_servicesą własnymi pętlami, które pozwalają na uruchomienie wielu wątków. Aby wesprzeć ten Boost.Asio wykonuje wewnętrzne blokowanie kosztem pewnej wydajności . Historia wersji Boost.Asio wskazuje, że wprowadzono kilka ulepszeń wydajności w celu zminimalizowania blokowania.

Pula wątków

  • libuv's udostępnia pulę wątków uv_queue_work. Rozmiar puli wątków można konfigurować za pomocą zmiennej środowiskowej UV_THREADPOOL_SIZE. Praca zostanie wykonana poza pętlą zdarzeń i w puli wątków. Po zakończeniu pracy moduł obsługi zakończenia będzie w kolejce do uruchomienia w pętli zdarzeń.
  • Chociaż Boost.Asio nie zapewnia puli wątków, io_servicemoże z łatwością działać jako jeden, ponieważ io_servicepozwala na wywołanie wielu wątków run. To nakłada na użytkownika odpowiedzialność za zarządzanie wątkami i ich zachowanie, co można zobaczyć w tym przykładzie.

Wątek i synchronizacja

  • libuv zapewnia abstrakcję wątków i typów synchronizacji.
  • Boost.Thread zapewnia typy wątków i synchronizacji. Wiele z tych typów jest ściśle zgodnych ze standardem C ++ 11, ale zawiera także pewne rozszerzenia. W wyniku Boost.Asio pozwalającego wielu wątkom na uruchomienie pojedynczej pętli zdarzeń, zapewnia ona łańcuchy jako sposób na utworzenie sekwencyjnego wywołania procedur obsługi zdarzeń bez użycia jawnych mechanizmów blokujących.

Operacje systemu plików

  • libuv zapewnia abstrakcję wielu operacji systemu plików. Istnieje jedna funkcja na operację, a każda operacja może być blokowaniem synchronicznym lub asynchronicznym. Jeśli zapewnione jest wywołanie zwrotne, operacja zostanie wykonana asynchronicznie w ramach wewnętrznej puli wątków. Jeśli połączenie zwrotne nie zostanie dostarczone, połączenie będzie blokowane synchronicznie.
  • Boost.Filesystem zapewnia synchroniczne wywołania blokujące dla wielu operacji systemu plików. Można je połączyć z Boost.Asio i pulą wątków, aby utworzyć asynchroniczne operacje systemu plików.

Networking

  • libuv obsługuje operacje asynchroniczne na gniazdach UDP i TCP, a także rozpoznawanie DNS. Twórcy aplikacji powinni mieć świadomość, że podstawowe deskryptory plików są ustawione na nieblokujące. Dlatego natywne operacje synchroniczne powinny sprawdzać zwracane wartości i errno dla EAGAINlub EWOULDBLOCK.
  • Boost.Asio jest nieco bogatszy w obsługę sieci. Oprócz wielu funkcji sieciowych libuv, Boost.Asio obsługuje gniazda SSL i ICMP. Ponadto Boost.Asio zapewnia synchroniczne operacje blokowania i synchroniczne operacje nieblokujące, oprócz operacji asynchronicznych. Istnieje wiele funkcji wolnostojących, które zapewniają typowe operacje wyższego poziomu, takie jak odczyt określonej liczby bajtów lub do momentu odczytania określonego znaku ogranicznika.

Sygnał

  • libuv zapewnia abstrakcję killi obsługę sygnałów z ich uv_signal_trodzajem i uv_signal_*działaniem.
  • Boost.Asio nie zapewnia abstrakcji kill, ale signal_setzapewnia obsługę sygnału.

IPC


Różnice API

Chociaż interfejsy API różnią się w zależności od samego języka, oto kilka kluczowych różnic:

Stowarzyszenie Operacji i Handlerów

W ramach Boost.Asio istnieje odwzorowanie jeden na jednego między operacją a modułem obsługi. Na przykład każda async_writeoperacja wywoła WriteHandler jeden raz. Dotyczy to wielu operacji i programów obsługi libuv. Jednak libuv uv_async_sendobsługuje mapowanie wiele do jednego. Wiele uv_async_sendwywołań może spowodować, że uv_async_cb zostanie wywołany jeden raz.

Łańcuchy połączeń vs. Pętle obserwatora

W przypadku zadań, takich jak odczyt ze strumienia / UDP, obsługa sygnałów lub oczekiwanie na liczniki czasu, asynchroniczne łańcuchy połączeń Boost.Asio są nieco bardziej wyraźne. W libuv tworzony jest obserwator, który określa zainteresowania w konkretnym wydarzeniu. Następnie uruchamiana jest pętla dla obserwatora, w której zapewnione jest wywołanie zwrotne. Po otrzymaniu zdarzenia zainteresowania zostanie wywołane oddzwonienie. Z drugiej strony Boost.Asio wymaga wykonania operacji za każdym razem, gdy aplikacja jest zainteresowana obsługą zdarzenia.

Aby zilustrować tę różnicę, oto asynchroniczna pętla odczytu z funkcją Boost.Asio, w której async_receivewywołanie zostanie wykonane wielokrotnie:

void start()
{
  socket.async_receive( buffer, handle_read ); ----.
}                                                  |
    .----------------------------------------------'
    |      .---------------------------------------.
    V      V                                       |
void handle_read( ... )                            |
{                                                  |
  std::cout << "got data" << std::endl;            |
  socket.async_receive( buffer, handle_read );   --'
}    

A oto ten sam przykład z libuv, gdzie handle_readjest wywoływany za każdym razem, gdy obserwator zauważy, że gniazdo ma dane:

uv_read_start( socket, alloc_buffer, handle_read ); --.
                                                      |
    .-------------------------------------------------'
    |
    V
void handle_read( ... )
{
  fprintf( stdout, "got data\n" );
}

Przydział pamięci

W wyniku asynchronicznych łańcuchów wywołań w Boost.Asio i obserwatorów w libuv alokacja pamięci często występuje w różnych momentach. W przypadku obserwatorów libuv odkłada alokację, dopóki nie otrzyma zdarzenia wymagającego pamięci do obsługi. Alokacja odbywa się poprzez wywołanie zwrotne użytkownika, wywoływane wewnętrznie w libuv i odracza odpowiedzialność aplikacji za dezalokację. Z drugiej strony wiele operacji Boost.Asio wymaga przydzielenia pamięci przed wykonaniem operacji asynchronicznej, na przykład w przypadku bufferfor async_read. Boost.Asio zapewnia null_buffers, że można go użyć do nasłuchiwania zdarzenia, umożliwiając aplikacjom odraczanie alokacji pamięci do momentu, gdy pamięć będzie potrzebna, chociaż jest to przestarzałe.

Ta różnica alokacji pamięci pojawia się również w bind->listen->acceptpętli. W libuv uv_listentworzy pętlę zdarzeń, która wywoła oddzwonienie użytkownika, gdy połączenie będzie gotowe do zaakceptowania. Umożliwia to aplikacji odroczenie alokacji klienta do momentu próby nawiązania połączenia. Z drugiej strony Boost.Asio listenzmienia tylko stan acceptor. W async_acceptnasłuchuje przypadku połączenia, a wymaga peer to być przydzielane przed wywoływany.


Występ

Niestety nie mam konkretnych liczb porównawczych do porównania libuv i Boost.Asio. Jednak zaobserwowałem podobną wydajność przy użyciu bibliotek w aplikacjach w czasie rzeczywistym i prawie w czasie rzeczywistym. Jeśli pożądane są liczby twarde, punktem odniesienia może być test porównawczy libuv .

Ponadto, mimo że należy przeprowadzić profilowanie w celu zidentyfikowania faktycznych wąskich gardeł, należy pamiętać o przydziałach pamięci. W przypadku libuv strategia alokacji pamięci ogranicza się przede wszystkim do wywołania zwrotnego alokatora. Z drugiej strony, API Boost.Asio nie pozwala na wywołanie zwrotne alokatora, a zamiast tego wypycha strategię alokacji do aplikacji. Jednak procedury obsługi / wywołania zwrotne w Boost.Asio mogą być kopiowane, przydzielane i zwalniane. Boost.Asio pozwala aplikacjom na zapewnienie niestandardowych funkcji alokacji pamięci w celu zaimplementowania strategii alokacji pamięci dla programów obsługi.


Dojrzałość

Boost.Asio

Rozwój Asio sięga co najmniej OCT-2004 i został przyjęty do Boost 1.35 22-MAR-2006 po przejściu 20-dniowej wzajemnej oceny. Służył również jako implementacja referencyjna i API dla Networking Library Propission for TR2 . Boost.Asio ma sporo dokumentacji , chociaż jego użyteczność różni się w zależności od użytkownika.

Interfejs API ma również dość spójne działanie. Ponadto operacje asynchroniczne są jawne w nazwie operacji. Na przykład acceptblokuje synchronicznie i async_acceptjest asynchroniczny. Interfejs API zapewnia bezpłatne funkcje do typowych zadań we / wy, na przykład czytanie ze strumienia, aż do \r\nodczytu. Zwrócono również uwagę na ukrycie niektórych szczegółów dotyczących sieci, takich jak ip::address_v4::any()reprezentacja adresu „wszystkich interfejsów” 0.0.0.0.

Wreszcie, Boost 1.47+ zapewnia śledzenie procedur obsługi , które mogą okazać się przydatne podczas debugowania, a także obsługę C ++ 11.

libuv

Na podstawie ich wykresów github rozwój Node.js sięga co najmniej FEB-2009 , a rozwój libuv - MAR-2011 . Książka uvbook to świetne miejsce na wprowadzenie libuv. Dokumentacja API jest tutaj .

Ogólnie rzecz biorąc, interfejs API jest dość spójny i łatwy w użyciu. Jedną z anomalii, które mogą być źródłem nieporozumień, jest uv_tcp_listentworzenie pętli obserwatora. Jest to inaczej niż w innych obserwatorów, które mają zwykle uv_*_starti uv_*_stopparę funkcji kontrolowania trwałości pętli obserwatora. Ponadto niektóre uv_fs_*operacje mają przyzwoitą liczbę argumentów (do 7). Dzięki określeniu zachowania synchronicznego i asynchronicznego na podstawie wywołania zwrotnego (ostatniego argumentu) widoczność zachowania synchronicznego może zostać zmniejszona.

Wreszcie, szybkie spojrzenie na historię zatwierdzania libuv pokazuje, że programiści są bardzo aktywni.


2
Dzięki! Świetna odpowiedź! Nie mogę wymyślić nic bardziej kompleksowego :)
Viet

1
Bardzo zadowolony z odpowiedzi, nagradzam cię nagrodą :) Pozwól SO zdecydować najlepszą odpowiedź dla siebie.
Wietnam

28
Niesamowita odpowiedź. Obejmuje to zarówno obraz wysokiego poziomu, jak i specyficzne, ważne różnice w szczegółach (np. Gwintowanie / pętla zdarzeń). Dziękuję Ci bardzo!
oberstet

1
@oberstet: Nie. Zaktualizowałem odpowiedź, aby wspomnieć, że większość operacji libuv odbywa się jeden na jednego. Jednak libuv może gromadzić wiele uv_async_sendpołączeń i obsługiwać je wszystkie za pomocą jednego połączenia zwrotnego. Jest to udokumentowane tutaj . Dziękuję również wszystkim.
Tanner Sansbury,

2
Wewnętrzne blokowanie pętli zdarzeń w trybie Boost.Asio wygląda przerażająco z punktu widzenia wydajności. Jak może mieć podobną wydajność do libuv bez blokady? Może pomocne może być dodanie ostrzeżenia w sekcji wydajności.
zeodtr

46

Dobrze. Mam pewne doświadczenie w korzystaniu z obu bibliotek i potrafię wyjaśnić niektóre rzeczy.

Po pierwsze, z koncepcyjnego punktu widzenia biblioteki te mają zupełnie inną budowę. Mają różne architektury, ponieważ mają różną skalę. Boost.Asio to duża biblioteka sieciowa przeznaczona do użycia z protokołami TCP / UDP / ICMP, POSIX, SSL i tak dalej. Libuv to przede wszystkim warstwa do wieloplatformowej abstrakcji IOCP dla Node.js. Tak więc libuv jest funkcjonalnie podzbiorem Boost.Asio (wspólne funkcje tylko wątki TCP / UDP Sockets, timery). W takim przypadku możemy porównać te biblioteki przy użyciu tylko kilku kryteriów:

  1. Integracja z Node.js - Libuv jest znacznie lepszy, ponieważ jest do tego przeznaczony (możemy go w pełni zintegrować i używać we wszystkich aspektach, na przykład w chmurze, np. Lazur Windows). Ale Asio implementuje również prawie taką samą funkcjonalność jak w środowisku sterowanym kolejką zdarzeń Node.js.
  2. Wydajność IOCP - nie widziałem dużych różnic, ponieważ obie te biblioteki wyodrębniają podstawowe API systemu operacyjnego. Ale robią to w inny sposób: Asio intensywnie wykorzystuje funkcje C ++, takie jak szablony, a czasem TMP. Libuv jest rodzimą biblioteką C. Niemniej jednak realizacja IOCP przez Asio jest bardzo skuteczna. Gniazda UDP w Asio nie są wystarczająco dobre, lepiej dla nich użyć libuv.

    Integracja z nowymi funkcjami C ++: Asio jest lepsze (Asio 1.51 intensywnie korzysta z modelu asynchronicznego C ++ 11, semantyki przenoszenia, szablonów variadic). Pod względem dojrzałości Asio jest bardziej stabilnym i dojrzałym projektem z dobrą dokumentacją (jeśli porównać go z libuv opis nagłówków), wiele informacji w Internecie (rozmowy wideo, blogi: http://www.gamedev.net/blog/950/entry-2249317-a-guide-to-getting-started-with-boostasio?pg = 1 itd.), A nawet książki (nie dla profesjonalistów, ale mimo to: http://en.highscore.de/cpp/boost/index.html ). Libuv ma tylko jedną książkę online (ale także dobrą) http://nikhilm.github.com/uvbook/index.htmli kilka rozmów wideo, więc trudno będzie poznać wszystkie sekrety (ta biblioteka ma ich wiele). Aby uzyskać bardziej szczegółowe omówienie funkcji, zobacz moje komentarze poniżej.

Podsumowując, powinienem powiedzieć, że wszystko zależy od twoich celów, twojego projektu i tego, co konkretnie zamierzasz zrobić.


11
Liczy się twoje umiejętności techniczne i doświadczenie. Serdeczne pozdrowienia od Kubańczyka.
dsign

2
Zgadzam się ze wszystkimi twoimi punktami oprócz dokumentacji Asio. Oficjalna dokumentacja nie oddaje sprawiedliwości tej cudownej bibliotece. Istnieje wiele innych dokumentów i przemówienie autora od autora, które uważam za bardzo przydatne. I nie natknąłem się na książkę dla Asio. Czy możesz to powiązać w swojej odpowiedzi? To będzie bardzo pomocne.
Vikas

@vikas Tak Zgadzam się, że dokumentacja jest kiepska i czasami sprzeczna, ale w porównaniu z libuv miło jest zacząć. Jeśli chodzi o książki, edytuję swoją odpowiedź, ale myślę, że już ją widziałeś (niestety nie ma książki poświęconej całkowicie ulepszeniu - rozproszona tylko informacje)
Ołeksandr Karaberow

Co rozumiesz przez „Więc libuv jest funkcjonalnie podzbiorem Boost.Asio (TCP / UDP / Sockets i wątki)”? Według TOC nikhilm.github.com/uvbook/index.html libuv ma szerszą aplikację niż boost :: asio.
Siergiej Nikulow

7
@AlexanderKaraberov, czy mógłbyś rozwinąć problemy ASIO z UDP?
Bruno Martinez,


2

Dodanie statusu przenośności: od opublikowania tej odpowiedzi i zgodnie z moimi próbami:

  • Boost.ASIO nie ma oficjalnego wsparcia dla iOS i Androida, np. Jego system kompilacji nie działa na iOS od razu po wyjęciu z pudełka.
  • libuv łatwo się buduje na iOS i Androida, z oficjalną obsługą Androida bezpośrednio w swoich dokumentach . Mój własny ogólny skrypt kompilacji iOS dla projektów opartych na Autotools działa bez problemów.
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.