Właśnie obejrzałem następujący film: Wprowadzenie do Node.js i nadal nie rozumiem, w jaki sposób zyskujesz szybkość.
Głównie w pewnym momencie Ryan Dahl (twórca Node.js) mówi, że Node.js jest oparty na pętli zdarzeń zamiast na wątku. Wątki są drogie i należy je pozostawić wyłącznie ekspertom w dziedzinie programowania równoległego.
Później pokazuje stos architektury Node.js, który ma podstawową implementację C, która ma wewnętrznie własną pulę wątków. Oczywiście programiści Node.js nigdy nie wykopaliby własnych wątków ani nie korzystali bezpośrednio z puli wątków ... używają asynchronicznych wywołań zwrotnych. Tyle rozumiem.
Nie rozumiem tylko tego, że Node.js nadal używa wątków ... po prostu ukrywa implementację, więc jak to jest szybsze, jeśli 50 osób żąda 50 plików (obecnie nie w pamięci), więc nie jest wymaganych 50 wątków ?
Jedyna różnica polega na tym, że ponieważ jest on zarządzany wewnętrznie, programista Node.js nie musi kodować szczegółów wątków, ale pod nimi nadal używa wątków do przetwarzania żądań plików IO (blokujących).
Czy naprawdę nie bierzesz tylko jednego problemu (wątków) i ukrywasz go, dopóki ten problem nadal istnieje: głównie wiele wątków, przełączanie kontekstu, martwe blokady ... itd.?
Muszą być pewne szczegóły, których wciąż nie rozumiem.
źródło
select()
jest szybsze niż zamiana kontekstu wątku.Odpowiedzi:
W rzeczywistości jest tu kilka różnych rzeczy. Ale zaczyna się od memu, że wątki są po prostu bardzo trudne. Więc jeśli są twarde, bardziej prawdopodobne jest, że używając wątków do 1) zerwania z powodu błędów i 2) nie wykorzystania ich tak skutecznie, jak to możliwe. (2) to ten, o który pytasz.
Pomyśl o jednym z podanych przez niego przykładów, w którym pojawia się żądanie, a następnie uruchom zapytanie, a następnie zrób coś z jego wynikami. Jeśli napiszesz go w standardowy sposób, kod może wyglądać następująco:
Jeśli nadchodzące żądanie spowodowało utworzenie nowego wątku, który uruchomił powyższy kod, wątek będzie tam siedział, nic nie robiąc podczas
query()
działania. (Według Ryana Apache używa pojedynczego wątku do spełnienia pierwotnego żądania, podczas gdy nginx przewyższa go w przypadkach, o których mówi, ponieważ tak nie jest.)Teraz, jeśli jesteś naprawdę sprytny, możesz wyrazić powyższy kod w taki sposób, aby środowisko mogło się wyłączyć i zrobić coś innego podczas uruchamiania zapytania:
To jest właśnie to, co robi node.js. Zasadniczo dekorujesz - w sposób dogodny ze względu na język i środowisko, stąd uwagi na temat zamknięć - swój kod w taki sposób, aby środowisko mogło sprytnie wiedzieć, co się uruchamia i kiedy. W ten sposób node.js nie jest nowy w tym sensie, że wynalazł asynchroniczne operacje we / wy (nie dlatego, że ktoś tak twierdził), ale jest nowy, ponieważ sposób jego wyrażania jest nieco inny.
Uwaga: kiedy mówię, że środowisko może być sprytne w kwestii tego, co działa i kiedy, w szczególności mam na myśli to, że wątek użyty do uruchomienia niektórych operacji we / wy może być teraz używany do obsługi innych żądań lub obliczeń, które można wykonać równolegle lub uruchom inne równoległe wejścia / wyjścia. (Nie jestem pewien, czy węzeł jest wystarczająco zaawansowany, aby rozpocząć więcej pracy dla tego samego żądania, ale masz pomysł).
źródło
Uwaga! To stara odpowiedź. Choć w ogólnym zarysie jest to nadal prawdą, niektóre szczegóły mogły ulec zmianie ze względu na szybki rozwój Node w ciągu ostatnich kilku lat.
Używa wątków, ponieważ:
Aby sfałszować nie blokujące IO, niezbędne są wątki: wykonaj blokujące IO w osobnym wątku. Jest to brzydkie rozwiązanie i powoduje znaczne koszty.
Jest jeszcze gorzej na poziomie sprzętowym:
To jest po prostu głupie i nieefektywne. Ale to działa przynajmniej! Możemy cieszyć się Node.js, ponieważ ukrywa brzydkie i nieporęczne szczegóły za architekturą asynchroniczną sterowaną zdarzeniami.
Może ktoś zaimplementuje O_NONBLOCK dla plików w przyszłości? ...
Edycja: Dyskutowałem o tym z przyjacielem, który powiedział mi, że alternatywą dla wątków jest odpytywanie za pomocą select : określ limit czasu 0 i wykonaj IO na zwróconych deskryptorach plików (teraz, gdy gwarantuje się, że nie będą blokować).
źródło
Obawiam się, że „robię coś złego” tutaj, jeśli tak, usuń mnie i przepraszam. W szczególności nie widzę, jak tworzę zgrabne małe adnotacje, które stworzyli niektórzy ludzie. Mam jednak wiele obaw / spostrzeżeń dotyczących tego wątku.
1) Skomentowany element w pseudokodzie w jednej z popularnych odpowiedzi
jest zasadniczo fałszywy. Jeśli wątek oblicza, to nie kręci kciukami, wykonuje niezbędną pracę. Z drugiej strony, jeśli po prostu czeka na zakończenie IO, to nie jest wykorzystuje czasu procesora, cały punkt infrastruktury kontroli wątków w jądrze polega na tym, że CPU znajdzie coś pożytecznego do zrobienia. Jedynym sposobem na „poruszenie kciukami”, jak sugerowano tutaj, byłoby utworzenie pętli odpytywania, a nikt, kto zakodował prawdziwy serwer sieciowy, nie jest na to wystarczająco przygotowany.
2) „Wątki są trudne”, ma sens tylko w kontekście udostępniania danych. Jeśli masz zasadniczo niezależne wątki, tak jak ma to miejsce w przypadku obsługi niezależnych żądań internetowych, to wątkowanie jest banalnie proste, po prostu kodujesz liniowy przepływ obsługi jednego zadania i siedzisz całkiem wiedząc, że obsłuży on wiele żądań i każdego będzie skutecznie niezależny. Osobiście zaryzykowałbym to, że dla większości programistów nauka mechanizmu zamykania / oddzwaniania jest bardziej złożona niż zwykłe kodowanie wersji wątku od góry do dołu. (Ale tak, jeśli musisz komunikować się między wątkami, życie staje się naprawdę trudne naprawdę szybko, ale nie jestem przekonany, że mechanizm zamykania / oddzwaniania naprawdę to zmienia, po prostu ogranicza twoje opcje, ponieważ takie podejście jest wciąż możliwe do osiągnięcia dzięki wątkom W każdym razie, że ”
3) Jak dotąd nikt nie przedstawił żadnych prawdziwych dowodów na to, dlaczego jeden konkretny typ zmiany kontekstu byłby mniej lub bardziej czasochłonny niż jakikolwiek inny typ. Moje doświadczenie w tworzeniu wielozadaniowych jąder (na małą skalę dla wbudowanych kontrolerów, nic tak wymyślnego jak „prawdziwy” system operacyjny) sugeruje, że tak nie byłoby.
4) Wszystkie ilustracje, które do tej pory widziałem, które pokazują, jak szybszy jest Węzeł niż inne serwery WWW, są strasznie wadliwe, jednak są one wadliwe w sposób, który pośrednio ilustruje jedną korzyść, którą zdecydowanie zaakceptowałbym dla Węzła (i to nie jest wcale nieistotne). Węzeł nie wygląda tak, jakby wymagał (a nawet nie zezwala) na dostrojenie. Jeśli masz model gwintowany, musisz utworzyć wystarczającą liczbę wątków, aby obsłużyć oczekiwane obciążenie. Zrób to źle, a skończysz na niskiej wydajności. Jeśli jest za mało wątków, procesor jest bezczynny, ale nie jest w stanie zaakceptować większej liczby żądań, utworzyć zbyt wiele wątków, a ty zmarnujesz pamięć jądra, aw przypadku środowiska Java również zmarnujesz pamięć główną sterty . W przypadku Javy marnowanie sterty jest pierwszym, najlepszym sposobem na zwiększenie wydajności systemu, ponieważ wydajne zbieranie śmieci (obecnie może się to zmienić w przypadku G1, ale wydaje się, że jury wciąż nie jest w tym punkcie co najmniej na początku 2013 r.) zależy od dużej ilości zapasów. Więc jest problem, dostrój go za mało wątków, masz bezczynne procesory i słabą przepustowość, dostrój go za dużo, i zapada się na inne sposoby.
5) Jest inny sposób, w jaki akceptuję logikę twierdzenia, że podejście Węzła „jest z założenia szybsze” i to jest to. Większość modeli wątków wykorzystuje model przełącznika kontekstu podzielony na przedziały czasowe, nałożony na bardziej odpowiedni (alert oceny wartości :) i bardziej wydajny (a nie ocena wartości) modelu zapobiegawczego. Dzieje się tak z dwóch powodów: po pierwsze, większość programistów wydaje się nie rozumieć pierwszeństwa z wyprzedzeniem, a po drugie, jeśli nauczysz się wątkowania w środowisku Windows, tworzenie czasów jest takie, czy ci się to podoba, czy nie (oczywiście, to wzmacnia pierwszy punkt ; przede wszystkim w pierwszych wersjach Javy zastosowano priorytetowe zapobieganie implementacjom Solaris i systemowi timeslicing w Windows. Ponieważ większość programistów nie rozumiała i narzekała, że „wątkowanie nie działa w Solaris” wszędzie zmienili model na przedziały czasu). W każdym razie najważniejsze jest to, że tworzenie czasów powoduje dodatkowe (i potencjalnie niepotrzebne) przełączniki kontekstu. Każdy przełącznik kontekstu zajmuje czas procesora i ten czas jest skutecznie usuwany z pracy, którą można wykonać na rzeczywistym zadaniu. Jednak ilość czasu zainwestowanego w zmianę kontekstu z powodu tworzenia przedziału czasu nie powinna przekraczać bardzo małego odsetka całkowitego czasu, chyba że dzieje się coś dziwacznego i nie widzę powodu, dla którego mogę oczekiwać, że tak będzie w przypadku prosty serwer WWW). Tak, tak, przełączniki nadmiaru kontekstu biorące udział w tworzeniu fragmentów są nieefektywne (i nie zdarzają się one w i czas ten jest skutecznie usuwany z pracy, którą można wykonać na prawdziwej pracy pod ręką. Jednak ilość czasu zainwestowanego w zmianę kontekstu z powodu tworzenia przedziału czasu nie powinna przekraczać bardzo małego odsetka całkowitego czasu, chyba że dzieje się coś dziwacznego i nie widzę powodu, dla którego mogę oczekiwać, że tak będzie w przypadku prosty serwer WWW). Tak, tak, przełączniki nadmiaru kontekstu biorące udział w tworzeniu fragmentów są nieefektywne (i nie zdarzają się one w i czas ten jest skutecznie usuwany z pracy, którą można wykonać na prawdziwej pracy pod ręką. Jednak ilość czasu zainwestowanego w zmianę kontekstu z powodu tworzenia przedziału czasu nie powinna przekraczać bardzo małego odsetka całkowitego czasu, chyba że dzieje się coś dziwacznego i nie widzę powodu, dla którego mogę oczekiwać, że tak będzie w przypadku prosty serwer WWW). Tak, tak, przełączniki nadmiaru kontekstu biorące udział w tworzeniu fragmentów są nieefektywne (i nie zdarzają się one wwątki jądra z reguły, btw), ale różnica będzie wynosić kilka procent przepustowości, a nie rodzaj liczby całkowitej, która jest sugerowana w oświadczeniach dotyczących wydajności, które są często sugerowane dla Węzła.
W każdym razie przepraszam, że to wszystko jest długie i chaotyczne, ale naprawdę czuję, że do tej pory dyskusja niczego nie udowodniła i chętnie usłyszę od kogoś w którejkolwiek z tych sytuacji:
a) prawdziwe wyjaśnienie, dlaczego Node powinien być lepszy (poza dwoma scenariuszami, które przedstawiłem powyżej, z których pierwszy (słabe dostrojenie) uważam za prawdziwe wyjaśnienie wszystkich testów, które do tej pory widziałem. [[edytuj ], im więcej o tym myślę, tym bardziej zastanawiam się, czy pamięć używana przez ogromną liczbę stosów może być tutaj znacząca. Domyślne rozmiary stosów dla współczesnych wątków bywają dość duże, ale pamięć przydzielana przez system zdarzeń oparty na zamknięciu byłby tylko tym, czego potrzeba)
b) prawdziwy test porównawczy, który faktycznie daje uczciwą szansę wybranemu serwerowi wątkowemu. Przynajmniej w ten sposób musiałbym przestać wierzyć, że twierdzenia są zasadniczo fałszywe;> ([edytuj] jest to prawdopodobnie silniejsze niż zamierzałem, ale wydaje mi się, że wyjaśnienia dotyczące korzyści w zakresie wydajności są w najlepszym razie niepełne, a przedstawione testy porównawcze są nieuzasadnione).
Na zdrowie, Toby
źródło
open()
nie może zostać zablokowany?). W ten sposób amortyzuje każde uderzenie wydajności, w którym tradycyjny modelfork()
/ napthread_create()
żądanie musiałby tworzyć i niszczyć wątki. I, jak wspomniano w PostScript a), amortyzuje to również problem z miejscem na stosie. Prawdopodobnie możesz obsłużyć tysiące żądań z, powiedzmy, 16 wątkami We / Wy w porządku.Ryan używa wątków dla części, które blokują (większość pliku node.js korzysta z nieblokującego IO), ponieważ niektóre części są szalenie trudne do napisania bez blokowania. Ale wierzę, że Ryan marzy o tym, aby wszystko było nieblokujące. Na slajdzie 63 (projekt wewnętrzny) widać, że Ryan używa biblioteki libev (biblioteka, która wyodrębnia asynchroniczne powiadamianie o zdarzeniach) dla nieblokującej pętli zdarzeń . Z powodu pętli zdarzeń node.js potrzebuje mniej wątków, co zmniejsza przełączanie kontekstu, zużycie pamięci itp.
źródło
Wątki są używane tylko do obsługi funkcji niemających funkcji asynchronicznej, takich jak
stat()
.stat()
Funkcja blokująca jest zawsze tak node.js musi wykorzystać, aby wykonać gwint rzeczywiste połączenia, bez blokowania głównego wątku (pętla zdarzenia). Potencjalnie żaden wątek z puli wątków nigdy nie zostanie użyty, jeśli nie trzeba wywoływać tego rodzaju funkcji.źródło
Nic nie wiem o wewnętrznych działaniach node.js, ale widzę, jak użycie pętli zdarzeń może przewyższyć obsługę wątkowych operacji we / wy. Wyobraź sobie żądanie płyty, podaj mi staticFile.x, zrób 100 żądań dla tego pliku. Każde żądanie zwykle zajmuje wątek pobierający ten plik, czyli 100 wątków.
Teraz wyobraź sobie, że pierwsze żądanie tworzy jeden wątek, który staje się obiektem wydawcy, wszystkie 99 innych żądań najpierw sprawdza, czy istnieje obiekt wydawcy dla staticFile.x, jeśli tak, wysłuchaj go podczas pracy, w przeciwnym razie rozpocznij nowy wątek, a tym samym nowy obiekt wydawcy.
Po zakończeniu pojedynczego wątku przekazuje staticFile.x do wszystkich 100 detektorów i sam się niszczy, więc następne żądanie tworzy nowy nowy wątek i obiekt wydawcy.
Tak więc w powyższym przykładzie jest to 100 wątków vs 1 wątek, ale także wyszukiwanie 1 dysku zamiast 100 wyszukiwania dysku, zysk może być dość fenomenalny. Ryan jest mądrym facetem!
Innym sposobem na spojrzenie jest jeden z jego przykładów na początku filmu. Zamiast:
Ponownie 100 oddzielnych zapytań do bazy danych w porównaniu do ...:
Jeśli zapytanie już trwa, inne równe zapytania po prostu wskoczą na modę, dzięki czemu możesz mieć 100 zapytań w jednej rundzie bazy danych.
źródło