Wydajność Node.js a .Net

183

Dużo czytałem o tym, że Node.js jest szybki i jest w stanie pomieścić duże ilości obciążenia. Czy ktoś ma jakieś prawdziwe dowody na to w porównaniu do innych frameworków, szczególnie .Net? Większość artykułów, które przeczytałem, jest niepotwierdzonych lub nie ma porównań z .Net.

Dzięki

David Merrilees
źródło
1
Czy mógłbyś być bardziej precyzyjny w jakim scenariuszu mówimy?
Marcus Granström,
1
Interesuje mnie każde porównanie wydajności .Net i Node.js dla porównywalnych aplikacji internetowych działających w IIS.
David Merrilees,
1
Nie mogę sobie wyobrazić, żeby ktoś budował witrynę internetową o wysokiej wydajności. wymagania poza .Net. Najbardziej podstawowym problemem, na jaki się natkniesz, jest to, że nie będzie on zbyt opłacalny pod względem licencjonowania od czasu wysokiej wydajności. witryny zazwyczaj wymagają skalowania. I nie, nie jestem hejterem .Net. .Net płaci rachunki.
Shane Courtrille,
4
Musiałem przeprowadzić wewnętrzne testy małego interfejsu API REST przy użyciu Node / express / mongo i nowego .net webapi / mongo. Występowały różnice w zależności od oczekiwań klienta, ale na koniec dnia nie wystarczyło, aby zrobić różnica. Musisz opracować własne testy w oparciu o własne scenariusze. Napisanie różnych interfejsów API w obu językach zajęło nam trzy dni, a następnie kolejne kilka dni, aby poprawnie skonfigurować testowanie. Jeśli planujesz zrobić coś zdalnie poważnego, proponuję przeprowadzić testy w oparciu o twoje wymagania i sam zdecydować, który jest lepszy dla twojego obciążenia.
AlexGad
5
@ShaneCourtrille Mylicie .Net (framework) i Windows (system operacyjny). Są to bardzo różne rzeczy i NIE ma żadnych wymagań licencyjnych dla .Net (który działa całkiem nieźle na Linuksie jako Mono).
rainabba

Odpowiedzi:

366

Bycie SZYBKIM i radzenie sobie z dużą ilością ŁADUNKÓW to dwie różne rzeczy. Serwer, który naprawdę SZYBKO obsługuje jedno żądanie na sekundę, może całkowicie wykrzyczeć, jeśli wyślesz mu 500 żądań na sekundę (pod LOAD ).

Musisz także wziąć pod uwagę strony statyczne (i buforowane) vs. strony dynamiczne. Jeśli martwisz się o strony statyczne, to IIS prawdopodobnie pobije węzeł, ponieważ IIS używa buforowania w trybie jądra, co oznacza, że ​​żądania, które żądają strony statycznej, nawet nie wyjdą z jądra.

Zgaduję, że szukasz porównania między ASP.NET i węzłem. W tej bitwie, po skompilowaniu / zinterpretowaniu wszystkiego, zapewne będziesz blisko wydajności. Może .NET jest trochę SZYBCIEJ, a może węzeł jest trochę SZYBCIEJ , ale prawdopodobnie jest wystarczająco blisko, żebyś się tym nie przejmował. Postawiłbym na .NET, ale nie jestem pewien.

Miejsce, w którym węzeł jest naprawdę przekonujący, to obsługa LOAD . Właśnie tam technologie naprawdę się różnią. ASP.NET poświęca wątek dla każdego żądania ze swojej puli wątków, a gdy ASP.NET wyczerpie się, wszystkie dostępne żądania wątków zaczynają się kolejkować. Jeśli obsługujesz aplikacje „Hello World”, takie jak przykład @shankar, może to nie mieć większego znaczenia, ponieważ wątki nie będą blokowane i będziesz w stanie obsłużyć wiele żądań przed Tobą zabrakło wątków. Problem z modelem ASP.NET pojawia się, gdy zaczynasz wysyłać żądania We / Wy, które blokują wątek (wywoływanie DB, wysyłanie żądania HTTP do usługi, odczytywanie pliku z dysku). Te żądania blokowania oznaczają, że Twój cenny wątek z puli wątków nic nie robi. Im więcej blokujesz,ZAŁADUJ swoją aplikację ASP.NET będzie mogła służyć.

Aby zapobiec temu blokowaniu, korzystasz z portów zakończenia we / wy, które nie wymagają trzymania wątku podczas oczekiwania na odpowiedź. ASP.NET obsługuje to, ale niestety wiele popularnych frameworków / bibliotek w .NET DON'T. Na przykład ADO.NET obsługuje porty zakończenia we / wy, ale Entity Framework ich nie używa. Możesz więc zbudować aplikację ASP.NET, która jest czysto asynchroniczna i obsługuje wiele obciążeń, ale większość ludzi tego nie robi, ponieważ nie jest to tak łatwe jak zbudowanie aplikacji synchronicznej i możesz nie być w stanie użyć niektórych swoich ulubionych części frameworka (jak linq do encji), jeśli tak zrobisz.

Problem polega na tym, że ASP.NET (i .NET Framework) zostały stworzone, aby nie wyrażać opinii na temat asynchronicznych operacji we / wy. .NET nie dba o to, czy piszesz kod synchroniczny czy asynchroniczny, więc decyzja należy do programisty. Częściowo dlatego, że wątkowanie i programowanie za pomocą operacji asynchronicznych było uważane za „trudne”, a .NET chciał, aby wszyscy byli zadowoleni (noobowie i eksperci). Stało się to jeszcze trudniejsze, ponieważ .NET skończył z 3-4 różnymi wzorcami wykonywania asynchronizacji. .NET 4.5 próbuje wrócić i zmodernizować platformę .NET, aby mieć uparty model wokół asynchronicznego We / Wy, ale może upłynąć trochę czasu, zanim frameworki, na których Ci zależy, faktycznie ją obsługują.

Z drugiej strony projektanci węzła dokonali zdecydowanego wyboru, że WSZYSTKIE I / O powinny być asynchroniczne. Z powodu tej decyzji projektanci węzłów byli również w stanie podjąć decyzję, że każda instancja węzła będzie jednowątkowa, aby zminimalizować przełączanie wątków, i że jeden wątek po prostu wykona kod, który został umieszczony w kolejce. To może być nowe żądanie, może to być wywołanie zwrotne z żądania DB, może to być wywołanie zwrotne z wysłanego żądania HTTP. Węzeł próbuje zmaksymalizować wydajność procesora poprzez wyeliminowanie przełączników kontekstowych wątków. Ponieważ węzeł dokonał tego zdecydowanego wyboru, że WSZYSTKIE we / wy są asynchroniczne, oznacza to również, że wszystkie jego frameworki / dodatki obsługują ten wybór. Łatwiej jest pisać aplikacje, które są w 100% asynchroniczne w węźle (ponieważ węzeł zmusza do pisania aplikacji, które są asynchroniczne).

Ponownie nie mam twardych liczb, które mogłyby udowodnić w ten czy inny sposób, ale myślę, że węzeł wygrałby konkurs LOAD dla typowej aplikacji internetowej. Wysoce zoptymalizowana (w 100% asynchroniczna) aplikacja .NET może dać ekwiwalent aplikacji node.js za swoje pieniądze, ale jeśli weźmiesz średnio całą platformę .NET i wszystkie aplikacje węzłowe, średnio węzeł prawdopodobnie obsługuje więcej ZAŁADUJ.

Mam nadzieję, że to pomaga.

Matt Dotson
źródło
39
Pamiętaj, że ASP.NET od dawna wspiera procedury obsługi żądań asynchronicznych, a dzięki MVC4 stały się niezwykle proste w użyciu.
fabspro,
12
„Te żądania blokowania oznaczają, że Twój cenny wątek z puli wątków nic nie robi. Im więcej blokujesz, tym mniej OBCIĄŻ, które aplikacja ASP.NET będzie w stanie obsłużyć”. Dlaczego ma to znaczenie, czy ustawiamy się w kolejce z przodu (żądanie przychodzące), czy w backend (rzeczywisty wątek roboczy)? Bez względu na wszystko żądanie klienta czeka na odpowiedź. Myślę, że kluczem do tego, że ludzie pomijają tę debatę, jest „przepustowość”. Nie chodzi o to, ile równoczesnych połączeń serwer utrzymuje, o to, jak szybko może odpowiedzieć na każde żądanie, prawda?
sjdirect
19
// Nie pozwolę edytować mojego komentarza, więc oto, co chciałem powiedzieć .// @sjdirect - Przepustowość to nie to samo, co czas odpowiedzi. Masz rację, dbając o czas reakcji, ale jest to wybór między czasem w kolejce + czasem odpowiedzi lub po prostu czasem odpowiedzi. Przetwarzanie żądania zajmie tyle samo czasu w obu scenariuszach (Wykonanie synchroniczne NIE przyspieszy wykonania żądania DB), ale jeśli wątki żądania są zablokowane, to dodajesz również czas oczekiwania do żądań ponieważ nie można nawet rozpocząć przetwarzania żądania, dopóki poprzednie żądania nie zostaną wykonane.
Matt Dotson
6
To było naprawdę pouczające, dziękuję! Należy jednak zauważyć, że Entity Framework 6 (obecnie RC1) obsługuje teraz wzorzec asynchroniczny z .NET 4.5. msdn.microsoft.com/en-us/data/jj819165
parlament
4
To bardzo spekulacyjne! Byłoby wspaniale mieć dane. Zwykle tak decyduję, jak postępować z tematami dotyczącymi wydajności.
kingPuppy
50

Zrobiłem podstawowy test wydajności między nodejs i IIS. IIS jest około 2,5 razy szybszy niż nodejs, gdy rozprasza „cześć, świecie!”. kod poniżej.

mój sprzęt: Dell Latitude E6510, Core i5 (dwurdzeniowy), 8 GB RAM, 64-bitowy system operacyjny Windows 7 Enterprise

serwer węzłów

runs at http://localhost:9090/
/// <reference path="node-vsdoc.js" />
var http = require("http");
http.createServer(function (request, response) {
response.writeHead(200, { "Content-Type": "text/html" });
response.write("<p>hello, world!</p>");
response.end();
}).listen(9090);

default.htm

hosted by iis at http://localhost/test/
<p>hello, world!</p>

mój własny program testowy wykorzystujący bibliotekę zadań równoległych:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Net;
using System.Threading;
using System.Threading.Tasks;
using System.Diagnostics;

namespace HttpBench
{
class Program
{
    private int TotalCount = 100000;
    private int ConcurrentThreads = 1000;
    private int failedCount;
    private int totalBytes;
    private int totalTime;
    private int completedCount;
    private static object lockObj = new object();

    /// <summary>
    /// main entry point
    /// </summary>
    static void Main(string[] args)
    {
        Program p = new Program();
        p.Run(args);
    }

    /// <summary>
    /// actual execution
    /// </summary>
    private void Run(string[] args)
    {
        // check command line
        if (args.Length == 0)
        {
            this.PrintUsage();
            return;
        }
        if (args[0] == "/?" || args[0] == "/h")
        {
            this.PrintUsage();
            return;
        }

        // use parallel library, download data
        ParallelOptions options = new ParallelOptions();
        options.MaxDegreeOfParallelism = this.ConcurrentThreads;
        int start = Environment.TickCount;
        Parallel.For(0, this.TotalCount, options, i =>
            {
                this.DownloadUrl(i, args[0]);
            }
        );
        int end = Environment.TickCount;

        // print results
        this.Print("Total requests sent: {0}", true, this.TotalCount);
        this.Print("Concurrent threads: {0}", true, this.ConcurrentThreads);
        this.Print("Total completed requests: {0}", true, this.completedCount);
        this.Print("Failed requests: {0}", true, this.failedCount);
        this.Print("Sum total of thread times (seconds): {0}", true, this.totalTime / 1000);
        this.Print("Total time taken by this program (seconds): {0}", true, (end - start) / 1000);
        this.Print("Total bytes: {0}", true, this.totalBytes);
    }

    /// <summary>
    /// download data from the given url
    /// </summary>
    private void DownloadUrl(int index, string url)
    {
        using (WebClient client = new WebClient())
        {
            try
            {
                int start = Environment.TickCount;
                byte[] data = client.DownloadData(url);
                int end = Environment.TickCount;
                lock (lockObj)
                {
                    this.totalTime = this.totalTime + (end - start);
                    if (data != null)
                    {
                        this.totalBytes = this.totalBytes + data.Length;
                    }
                }
            }
            catch
            {
                lock (lockObj) { this.failedCount++; }
            }
            lock (lockObj)
            {
                this.completedCount++;
                if (this.completedCount % 10000 == 0)
                {
                    this.Print("Completed {0} requests.", true, this.completedCount);
                }
            }
        }
    }

    /// <summary>
    /// print usage of this program
    /// </summary>
    private void PrintUsage()
    {
        this.Print("usage: httpbench [options] <url>");
    }

    /// <summary>
    /// print exception message to console
    /// </summary>
    private void PrintError(string msg, Exception ex = null, params object[] args)
    {
        StringBuilder sb = new System.Text.StringBuilder();
        sb.Append("Error: ");
        sb.AppendFormat(msg, args);
        if (ex != null)
        {
            sb.Append("Exception: ");
            sb.Append(ex.Message);
        }
        this.Print(sb.ToString());
    }

    /// <summary>
    /// print to console
    /// </summary>
    private void Print(string msg, bool isLine = true, params object[] args)
    {
        if (isLine)
        {
            Console.WriteLine(msg, args);
        }
        else
        {
            Console.Write(msg, args);
        }
    }

}
}

i wyniki:

IIS: httpbench.exe http://localhost/test

Completed 10000 requests.
Completed 20000 requests.
Completed 30000 requests.
Completed 40000 requests.
Completed 50000 requests.
Completed 60000 requests.
Completed 70000 requests.
Completed 80000 requests.
Completed 90000 requests.
Completed 100000 requests.
Total requests sent: 100000
Concurrent threads: 1000
Total completed requests: 100000
Failed requests: 0
Sum total of thread times (seconds): 97
Total time taken by this program (seconds): 16
Total bytes: 2000000

nodejs: httpbench.exe http://localhost:9090/

Completed 10000 requests.
Completed 20000 requests.
Completed 30000 requests.
Completed 40000 requests.
Completed 50000 requests.
Completed 60000 requests.
Completed 70000 requests.
Completed 80000 requests.
Completed 90000 requests.
Completed 100000 requests.
Total requests sent: 100000
Concurrent threads: 1000
Total completed requests: 100000
Failed requests: 0
Sum total of thread times (seconds): 234
Total time taken by this program (seconds): 27
Total bytes: 2000000

wniosek: IIS jest szybszy niż nodejs około 2,5 razy (w systemie Windows). Jest to bardzo podstawowy test i w żadnym wypadku nie rozstrzygający. Ale uważam, że to dobry punkt wyjścia. Nodejs jest prawdopodobnie szybszy na innych serwerach internetowych, na innych platformach, ale na Windows IIS jest zwycięzcą. Programiści, którzy chcą przekonwertować swój ASP.NET MVC na nodejs, powinni pauzować i zastanowić się dwa razy, zanim przejdą dalej.

Zaktualizowano (17.05.2012) Tomcat (w systemie Windows) wydaje się pobijać IIS, około 3 razy szybciej niż IIS w usuwaniu statycznego HTML.

kocur

index.html at http://localhost:8080/test/
<p>hello, world!</p>

wyniki tomcat

httpbench.exe http://localhost:8080/test/
Completed 10000 requests.
Completed 20000 requests.
Completed 30000 requests.
Completed 40000 requests.
Completed 50000 requests.
Completed 60000 requests.
Completed 70000 requests.
Completed 80000 requests.
Completed 90000 requests.
Completed 100000 requests.
Total requests sent: 100000
Concurrent threads: 1000
Total completed requests: 100000
Failed requests: 0
Sum total of thread times (seconds): 31
Total time taken by this program (seconds): 5
Total bytes: 2000000

zaktualizowany wniosek: uruchomiłem program testowy wiele razy. Tomcat wydaje się być najszybszym serwerem do usuwania STATIC HTML NA WINDOWS.

Zaktualizowano (18.05.2012) Wcześniej miałem 100 000 żądań łącznie z 10 000 równoczesnych wniosków. Zwiększyłem go do 1 000 000 całkowitego zapotrzebowania i 100 000 jednoczesnych żądań. IIS pojawia się jako krzyczący zwycięzca, a Nodejs jest najgorszy. Poniżej zestawiłem wyniki:

NodeJS vs IIS vs Tomcat obsługujący STATIC HTML na WINDOWS.

Shankar
źródło
56
Porównujesz jabłka z kotami. Porównaj Node.js z ASP.NET MVC. Co najwyżej usługi IIS obsługują pliki statyczne, choć poważnie w to wątpię.
alessioalex
12
@alessioalex: Nie rozumiem, dlaczego to porównanie jest nieprawidłowe. Porównuję czasy odpowiedzi dla statycznego HTML. IIS wyrzuca statyczny HTML z default.htm, podczas gdy serwer nodejs wyrzuca ten sam ciąg, a IIS wychodzi na przód. Porównanie aplikacji ASP.NET MVC wymagałoby więcej wysiłku i czasu, i planuję to zrobić później.
Shankar
28
Ok, powiedz, że IIS lepiej obsługuje pliki statyczne w systemie Windows niż Node. IIS obsługuje tylko pliki statyczne i takie (jak Apache lub NGINX), Node robi o wiele więcej. Powinieneś porównywać ASP.NET MVC z Węzłem (odpytywanie bazy danych, pobieranie danych z usługi zewnętrznej itp.). Zobaczysz ogromny wzrost wydajności dzięki Node w porównaniu z ASP.NET MVC.
alessioalex
27
Jeśli masz zamiar to zrobić, proszę przynajmniej zrozumieć naturę węzła. Proces jednego węzła może wykorzystywać tylko jeden rdzeń. Porównujesz więc proces węzłowy działający na jednym rdzeniu z procesem IIS i tomcat wykorzystującym wiele rdzeni. Aby poprawnie porównać, musisz uruchomić klaster węzła. Zobacz nodejs.org/api/cluster.html, aby uzyskać proste w użyciu rozwiązanie klastrowe. Mogę jednak powiedzieć z doświadczenia, że ​​różnica między węzłem a asynchronicznym c # wynosi 10-15% w obu kierunkach, w zależności od tego, co robisz.
AlexGad
14
Ponadto testowanie plików statycznych za pomocą węzła, usług IIS i Tomcat jest bez znaczenia. Po pierwsze, węzeł nie jest świetny do plików statycznych, ale tak naprawdę nie jest przeznaczony (użyj odpowiedniego narzędzia do właściwej pracy). Jeśli ktoś martwi się szybkością swoich plików statycznych, i tak powinien korzystać z CDN.
AlexGad
26

Serwery NIO (Node.js itp.) Są zwykle szybsze niż serwery BIO. (IIS itp.). Aby poprzeć moje twierdzenie, TechEmpower jest firmą specjalizującą się w testach struktur sieciowych . Są bardzo otwarte i mają standardowy sposób testowania wszystkich ramek.

Testy rundy 9 są obecnie najnowsze (maj 2014). Testowanych jest wiele odmian IIS, ale pozbawiony aspnetu wydaje się być najszybszym wariantem IIS.

Oto wyniki w odpowiedziach na sekundę (im wyższa, tym lepiej):

  • Serializacja JSON
    • nodejs: 228,887
    • w paski aspnet: 105,272
  • Pojedyncze zapytanie
    • nodejs-mysql: 88,597
    • aspnet-stripped-raw: 47,066
  • Wiele zapytań
    • nodejs-mysql: 8,878
    • aspnet-stripped-raw: 3,915
  • Zwykły tekst
    • nodejs: 289,578
    • w paski aspnet: 109,136

We wszystkich przypadkach Node.js jest 2x szybszy niż IIS.

ttekin
źródło
1
Z wyjątkiem testu Multiple Queries, w którym ASPNET ma dwa wpisy (aspnet-stripped-raw i aspnet-mysql-raw), które pobiły nodejs-mysql, który jest najwyższym wpisem njs.
GalacticCowboy
4
Cóż, test wielu zapytań nie dokładnie testuje szybkość serwera. Głównie testuje szybkość sterownika MySQL. NodeJS używa głównie baz danych NO-SQL, takich jak MongoDB, CouchDB. Sterownik MySQL może nie zostać zoptymalizowany. Serializacja Json i testy tekstu jawnego dają czystą szybkość serwera - ufałbym im bardziej.
ttekin
co jeśli użyję węzła IIS? czy moje wyniki ulegną pogorszeniu lub będą takie same.
Umashankar
3
Dzięki za link do strony testu. Odpowiedź może jednak wymagać aktualizacji, ponieważ wraz z pojawieniem się platformy .NET Core 2.1 sytuacja mogła się nieco zmienić. Na przykład test serializacji JSON 2018 pokazuje listę ASP.NET Core przy 971,122 żądaniach / s i Node.js przy 561.593 żądaniach / s, więc dziś ASP.NET Core wydaje się pod tym względem prawie dwa razy szybszy niż Node.js.
stakx - nie przyczynia się już
13

Muszę się zgodzić z Marcusem Granstromem, scenariusz jest tutaj bardzo ważny.

Szczerze mówiąc, brzmi to tak, jakbyś podejmował decyzję architektoniczną o dużym wpływie. Moją radą byłoby wyodrębnienie obszarów budzących obawy i „odpalenie” między stosami, które rozważasz.

Pod koniec dnia jesteś odpowiedzialny za decyzję i nie sądzę, żeby wymówka „Jakiś facet na Stackoverflow pokazał mi artykuł, który powiedziałby, że będzie w porządku” Obetnie to swojemu szefowi.

Numer 9
źródło
1
Szukam czegoś, aby przekonać ludzi (w tym mojego szefa), że warto je rozważyć jako alternatywę dla strony MVC.net, a nie przekonać ich, że powinniśmy zamienić. Do tej pory znalazłem tylko anegdotyczne wzmianki o tym, że może on obsługiwać więcej obciążeń i działa lepiej. Czy ktoś faktycznie to udowodnił?
David Merrilees,
17
Ale co jest nie tak ze stroną MVC? DLACZEGO próbujesz znaleźć alternatywę? To najważniejsze Q. Jeśli problem polega na tym, że pies jest wolny przy dużym obciążeniu równoległym, powinieneś upewnić się, że używasz async.net. Jeśli nadal jest bardzo powolny, musisz rozbić kod i dowiedzieć się, gdzie są wąskie gardła. Z mojego doświadczenia wynika, że ​​nie ma ogromnej różnicy między węzłem a siecią asynchroniczną w scenariuszach REAL WORLD. Możesz zmienić platformę, ale prawdopodobnie po prostu zmienisz jeden zestaw wąskich gardeł / bólów kodu na inny zestaw wąskich gardeł / bólów kodu.
AlexGad
1

Główną różnicą, którą widzę, jest to, że węzeł .js jest dynamicznym językiem programowania (sprawdzanie typu), więc typy muszą być wyprowadzane w czasie wykonywania. Silnie typowane języki, takie jak C # .NET, teoretycznie mają znacznie większy potencjał, wygrywając w walce z Node .js (i PHP itp.), Szczególnie tam, gdzie jest to kosztowne obliczenie. Nawiasem mówiąc, .NET powinien mieć lepszą natywną współpracę z C / C ++ niż węzeł .js.

Ondrej Rozinek
źródło
4
Twoja sugestia, że ​​„słabe” pisanie w JS spowalnia ją, jest błędna i nieistotna i niezależnie od tego, to porównanie Jabłek i Kamieni (nawet Pomarańcze byłyby bardziej podobne niż to, co sugerujesz).
rainabba
7
@rainabba Kiedy porównujesz jakieś obliczenia (np. Fibonacciego z X), jest on całkowicie poprawny.
Stan
5
@steve Właściwie, biorąc pod uwagę Z, nadal nie możesz tego powiedzieć, ponieważ JS jest językiem, a .Net to framework. To są zupełnie różne rzeczy. Środowiska wykonawcze .Net są kompilowane dla konkretnej architektury procesora, więc nie można znacząco zmienić wydajności określonego fragmentu kodu dla pojedynczego elementu sprzętowego. Jak pokazuje V8, JS można interpretować i wykonywać z bardzo różnymi prędkościami i nie ma powodu sądzić, że któregoś dnia twój kod fibonacci napisany w JS nie uruchomi się JUST tak szybko, jak w przypadku kodu uruchomionego przez CLR (prawdopodobnie będzie to szybciej). Jabłka i kamienie; Tak jak powiedziałem.
rainabba
1
może masz rację, ale moim zdaniem nie znam innych krajów, w Chinach, wielu programistów, z którymi przeprowadziłem wywiady ze znanymi EF lub Linq do Sql, te frameworki znacznie zmniejszają wydajność .net
dexiang
1
To samo można powiedzieć JS. podczas gdy JS nadrabia zaległości w fibonacci, czy naprawdę uważasz, że .NET pozostanie tam, gdzie czeka?
quanben