Nieoczekiwany wynik testu wydajności node.js w porównaniu z ASP.NET Core

177

Robię szybki test warunków skrajnych na dwóch (trochę) napisanych projektach Hello World i . Oba działają w trybie produkcyjnym i bez dołączonego do nich loggera. Rezultat jest zdumiewający! ASP.NET core przewyższa aplikację node.js nawet po wykonaniu dodatkowej pracy, podczas gdy aplikacja node.js po prostu renderuje widok.

Aplikacja 1: http://localhost:3000/nodejs node.js

Wykorzystanie : node.js, express i vash renderowania silnika.

nodejs app

Kod w tym punkcie końcowym to

router.get('/', function(req, res, next) {
  var vm = {
    title: 'Express',
    time: new Date()
  }
  res.render('index', vm);
});

Jak widać, nie robi nic poza wysłaniem aktualnej daty przez timezmienną do widoku.

Aplikacja 2: http://localhost:5000/aspnet-core asp.net core

Używanie : ASP.NET Core, domyślne kierowanie na szablondnxcore50

Jednak ta aplikacja robi coś innego niż tylko renderowanie strony z datą. Generuje 5 akapitów różnych losowych tekstów. Powinno to teoretycznie uczynić to nieco cięższym niż aplikacja nodejs.

asp.net core app

Oto metoda akcji, która renderuje tę stronę

[ResponseCache(Location = ResponseCacheLocation.None, NoStore = true)]
[Route("aspnet-core")]
public IActionResult Index()
{
    var sb = new StringBuilder(1024);
    GenerateParagraphs(5, sb);

    ViewData["Message"] = sb.ToString();
    return View();
}

Wynik testu wysiłkowego

Wynik testu warunków skrajnych aplikacji Node.js.

Aktualizacja: zgodnie z sugestią Gorgi Koseva

Za pomocą npm install -g recluster-cli && NODE_ENV=production recluster-cli app.js 8

test nodejs 2

Wynik testu warunków skrajnych aplikacji ASP.NET Core

Wynik testu warunków skrajnych asp.net core

Nie mogę uwierzyć własnym oczom! Nie może być prawdą, że w tym podstawowym teście rdzeń asp.net jest znacznie szybszy niż nodejs. Oczywiście nie jest to jedyna miara używana do pomiaru wydajności między tymi dwoma technologiami sieciowymi, ale zastanawiam się, co robię źle po stronie node.js? .

Będąc profesjonalnym programistą asp.net i chcącym adaptować node.js w osobistych projektach, trochę mnie to zniechęca - ponieważ jestem trochę paranoikiem co do wydajności. Myślałem, że node.js jest szybszy niż rdzeń asp.net (ogólnie - jak widać w różnych innych testach porównawczych), po prostu chcę to sobie udowodnić (aby zachęcić się do adaptacji node.js).

Proszę o odpowiedź w komentarzu, jeśli chcesz, żebym dołączył więcej fragmentów kodu.

Aktualizacja: rozkład czasu aplikacji .NET Core

dystrybucja czasu aplikacji aspnetcore

Odpowiedź serwera

HTTP/1.1 200 OK
Cache-Control: no-store,no-cache
Date: Fri, 12 May 2017 07:46:56 GMT
Pragma: no-cache
Transfer-Encoding: chunked
Content-Type: text/html; charset=utf-8
Server: Kestrel
nieokreślony
źródło
52
„Zawsze myślałem, że node.js jest szybszy niż rdzeń asp.net” - jestem ciekaw, dlaczego tak myślisz? Nie widziałem żadnych testów porównawczych, które by to wspierały (głównymi powodami, które słyszałem dla przyjęcia node.js były „łatwość użycia” i „szybszy czas rozwoju / iteracji”)
UnholySheep
7
@UnholySheep To wszystko, co słyszałem kolego, słyszałem też, że jest „łatwy w użyciu” i „szybszy w rozwoju”, generalnie od ludzi nigdy nie działał w ASP.NET, zwłaszcza w VisualStudio. Nie chwalę się żadną technologią - ale to jest wzór, który zauważyłem.
undefined
3
O co tu chodzi? Jeśli jest to prawdopodobne: tak. techempower.com/benchmarks/ ... .... Ponadto zaktualizuj swój łańcuch narzędzi Dnxcore50 jest przestarzały przez co najmniej rok lub dwa.
Thomas
2
@Tony za pomocą modułu klastra NodeJs spawnuje wielu pracowników wykonujących i współdzielących obciążenie głównego procesu, który nasłuchuje na jednym procesie. Po prostu unika się konieczności konfigurowania wielu aplikacji na różnych portach. Ponadto, jeśli nodeJs działa w trybie klastra, wówczas powinna istnieć taka sama liczba aplikacji ASP.Net WebApplication działających w usługach IIS na portach diff i dzielić obciążenie między nimi za pośrednictwem systemu równoważenia obciążenia, wtedy będzie to prawidłowe porównanie.
Vipresh
36
Node.js jest świetny do wielu rzeczy, ale surowa prędkość na żądanie nie jest jedną z nich. To, w czym wyróżnia się, to bycie brokerem dla operacji I / O, ze względu na nieblokującą pętlę zdarzeń, która, gdy Node był nowy i błyszczący, była wielką sprawą. Oczywiście od tego czasu inne języki i frameworki nadrobiły zaległości, więc w .NET mamy bibliotekę zadań równoległych oraz asynchroniczne operacje we / wy i async / await. To, w czym Node nie wyróżnia się, to operacje związane z procesorem, takie jak renderowanie stron, ponieważ jest to jednowątkowy JavaScript.
Mark Rendle

Odpowiedzi:

188

Jak wspominało wielu innych, porównanie nie ma kontekstu.
W momencie jego wydania podejście asynchroniczne node.js było rewolucyjne. Od tego czasu inne języki i frameworki internetowe przyjęły podejście, które przyjęły do ​​głównego nurtu.

Aby zrozumieć, co oznaczała różnica, należy zasymulować żądanie blokujące, które reprezentuje pewne obciążenie we / wy, takie jak żądanie bazy danych. W systemie wątków na żądanie spowoduje to wyczerpanie puli wątków, a nowe żądania będą umieszczane w kolejce oczekującej na dostępny wątek.
W przypadku frameworków non-blocking-io tak się nie dzieje.

Rozważmy ten serwer node.js, który czeka 1 sekundę przed odpowiedzią

const server = http.createServer((req, res) => {
  setTimeout(() => {
    res.statusCode = 200;
    res.end();
  }, 1000);
});

Rzućmy teraz na to 100 równoczesnych koneksji przez 10 sekund. Spodziewamy się więc około 1000 wniosków do wykonania.

$ wrk -t100 -c100 -d10s http://localhost:8000
Running 10s test @ http://localhost:8000
  100 threads and 100 connections
  Thread Stats   Avg      Stdev     Max   +/- Stdev
    Latency     1.01s    10.14ms   1.16s    99.57%
    Req/Sec     0.13      0.34     1.00     86.77%
  922 requests in 10.09s, 89.14KB read
Requests/sec:     91.34
Transfer/sec:      8.83KB

Jak widać, mamy ukończone 922.

Rozważmy teraz następujący kod asp.net, napisany tak, jakby async / await nie były jeszcze obsługiwane, dlatego datuje się od ery uruchamiania node.js.

app.Run((context) =>
{
    Thread.Sleep(1000);
    context.Response.StatusCode = 200;
    return Task.CompletedTask;
});

$ wrk -t100 -c100 -d10s http://localhost:5000
Running 10s test @ http://localhost:5000
  100 threads and 100 connections
  Thread Stats   Avg      Stdev     Max   +/- Stdev
    Latency     1.08s    74.62ms   1.15s   100.00%
    Req/Sec     0.00      0.00     0.00    100.00%
  62 requests in 10.07s, 5.57KB read
  Socket errors: connect 0, read 0, write 0, timeout 54
Requests/sec:      6.16
Transfer/sec:     566.51B

62! Tutaj widzimy granicę puli wątków. Po dostrojeniu moglibyśmy uzyskać więcej jednoczesnych żądań, ale kosztem większej ilości zasobów serwera.

W przypadku tych obciążeń związanych z IO posunięcie w celu uniknięcia blokowania wątków przetwarzania było tak dramatyczne.

Teraz przenieśmy to do dnia dzisiejszego, gdzie ten wpływ rozchodził się w branży i pozwolił dotnet na skorzystanie z jego ulepszeń.

app.Run(async (context) =>
{
    await Task.Delay(1000);
    context.Response.StatusCode = 200;
});

$ wrk -t100 -c100 -d10s http://localhost:5000
Running 10s test @ http://localhost:5000
  100 threads and 100 connections
  Thread Stats   Avg      Stdev     Max   +/- Stdev
    Latency     1.01s    19.84ms   1.16s    98.26%
    Req/Sec     0.12      0.32     1.00     88.06%
  921 requests in 10.09s, 82.75KB read
Requests/sec:     91.28
Transfer/sec:      8.20KB

Żadnych niespodzianek, teraz dopasowujemy node.js.

Więc co to wszystko oznacza?

Twoje wrażenia, że ​​node.js jest „najszybszy” pochodzą z epoki, w której już nie żyjemy. Dodaj do tego, że to nigdy node / js / v8 nie były „szybkie”, chodziło o to, że zepsuły wątek na żądanie Model. Wszyscy inni nadrabiają zaległości.

Jeśli Twoim celem jest jak najszybsze przetwarzanie pojedynczych żądań, przyjrzyj się poważnym testom porównawczym, zamiast wprowadzać własne. Ale jeśli zamiast tego chcesz po prostu coś, co dostosowuje się do współczesnych standardów, wybierz dowolny język i upewnij się, że nie blokujesz tych wątków.

Zastrzeżenie: cały kod napisany i przetestowany na starzejącym się MacBooku Air w senny niedzielny poranek. Pobierz kod i wypróbuj go w systemie Windows lub dostosuj do swoich potrzeb - https://github.com/csainty/nodejs-vs-aspnetcore

Chris Sainty
źródło
35
NodeJs nigdy nie był unikatowy, model Thread per request istniał również w Asp.Net przed wprowadzeniem nodejs. Wszystkie metody, które wykonywały operacje we / wy, miały dwie wersje synchroniczne i asynchroniczne dostarczane przez Framework, ich metody ASYNC kończyły się słowem kluczowym „Async” dla na przykład. methodNameAsync
Vipresh
Jako np. Możesz odnieść się do tego artykułu związanego z operacjami bazy danych od 2008 codedigest.com/Articles/ADO/...
Vipresh
4
„podejścia, które przyjęli do głównego nurtu” - niewiele rzeczy jest wyjątkowych, stawiają sprawę przed znacznie szerszą publicznością. Posiadanie dostępnego podejścia i pieczenie go jako podstawowej zasady to dwie bardzo różne rzeczy.
Chris Sainty
4
Najlepsza odpowiedź tutaj. Kropka.
Narvalex
3
@LeeBrindley Nie zgadzam się, to nie jest próba zademonstrowania maksymalnej przepustowości danego sprzętu, ale pokazanie różnicy między blokowaniem a nieblokowaniem. Jeśli chcesz porównać surowe przepustowości, łączę się z techempower.
Chris Sainty
14

Struktury węzłów, takie jak Express i Koa, mają straszny narzut. Węzeł „Raw” jest znacznie szybszy.

Nie próbowałem tego, ale jest nowszy framework, który jest bardzo zbliżony do wydajności węzła „Raw”: https://github.com/aerojs/aero

(zobacz benchmark na tej stronie)

aktualizacja: Oto kilka liczb: https://github.com/blitzprog/webserver-benchmarks

Node:
    31336.78
    31940.29
Aero:
    29922.20
    27738.14
Restify:
    19403.99
    19744.61
Express:
    19020.79
    18937.67
Koa:
    16182.02
    16631.97
Koala:
    5806.04
    6111.47
Hapi:
    497.56
    500.00

Jak widać, narzuty w najpopularniejszych frameworkach node.js są BARDZO znaczące!

smorgs
źródło
5
jakie są liczby? Wyżej, tym lepiej?
Iamisti