200

I am doing a quick stress test on two (kinda) hello world projects written in and . Both of them are running in production mode and without a logger attached to them. The result is astonishing! ASP.NET core is outperforming node.js app even after doing some extra work whereas the node.js app is just rendering a view.

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

Using: node.js, express and vash rendering engine.

nodejs app

The code in this endpoint is

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

As you can see, it does nothing apart from sending current date via the time variable to the view.

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

Using: ASP.NET Core, default template targeting dnxcore50

However this app does something other than just rendering a page with a date on it. It generates 5 paragraphs of various random texts. This should theoretically make this little bit heavier than the nodejs app.

asp.net core app

Here is the action method that render this page

[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();
}

Stress test result

Node.js App stress test result

Update: Following suggestion by Gorgi Kosev

Using npm install -g recluster-cli && NODE_ENV=production recluster-cli app.js 8

nodejs test 2

ASP.NET Core App stress test result

asp.net core stress test result

Can't believe my eyes! It can't be true that in this basic test asp.net core is way faster than nodejs. Off course this is not the only metric used to measure performance between these two web technologies, but I am wondering what am I doing wrong in the node.js side?.

Being a professional asp.net developer and wishing to adapt node.js in personal projects, this is kind of putting me off - as I'm a little paranoid about performance. I thought node.js is faster than asp.net core (in general - as seen in various other benchmarks) I just want to prove it to myself (to encourage myself in adapting node.js).

Please reply in comment if you want me to include more code snippets.

Update: Time distribution of .NET Core app

aspnetcore app time distribution

Server response

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
Community
  • 1
  • 1
undefined
  • 2,939
  • 4
  • 23
  • 35
  • What's the time distribution like on the .NET Core side of things? 0.00 ms min time sets off alarm bells. Kestrel is fast, but I don't think they've implemented a quantum version yet. – Polynomial May 11 '17 at 17:57
  • @Polynomial It's strange to me too! I ran the test again but still the min time is 0ms. I'm using WebSurge. Here is the Request per second chart: http://imgur.com/a/JIOJf I also updated the question with Time distribution and server response. Let me know if that's what you wanted? . Thanks – undefined May 12 '17 at 07:51
  • 65
    *"I always thought node.js is faster than asp.net core"* - I'm curious why you think that? I've not seen any benchmarks that would support this (the main reasons I've heard for adopting node.js were "ease of use" and "faster development/iteration time") – UnholySheep May 12 '17 at 08:02
  • 8
    @UnholySheep It's all what I heard mate, I also heard it's "easy to use" and "faster to develop" too, generally from people use never worked in ASP.NET, especially in VisualStudio. I'm not bragging about any technology - but this is the pattern I noticed. – undefined May 12 '17 at 08:29
  • 1
    So, why are you nerfing .Net? – Kobi May 18 '17 at 10:35
  • 3
    What is the question here? If it is plausible: Yes it is. https://www.techempower.com/benchmarks/#section=data-r14&hw=ph&test=plaintext .... Also update your toolchain Dnxcore50 is outdated for at least a year or two. – Thomas May 18 '17 at 10:48
  • 1
    You can use multiple cores with cluster module in Node.js... People always use a single process.... Please give a look at this https://github.com/Unitech/pm2 – Marco Talento May 18 '17 at 10:53
  • Some people at Microsoft are still surprised by their dark matter developers: https://mobile.twitter.com/davidfowl/status/865152077273546752 @davidfowl three years after .NET Core I still have 95% of my co-workers who have not checked it out or even heard of it. The node people however are fancy and management loves it for it's UI reuse. – Thomas May 18 '17 at 11:04
  • @IKnowNothingAtAll you misunderstood UnholySheep's question. .NET is JIT compiled IL, just like Java. After the first call, you are running native code. Why did you expect node to be faster? – Panagiotis Kanavos May 18 '17 at 11:16
  • @PanagiotisKanavos node/chrome/v8 is also jitting the javascript :) it just has a much harder life doing so due to the dynamic typing. – Thomas May 18 '17 at 11:20
  • 2
    @Thomas it's jitting the *Javascript source*. Java and ASP.NET work with bytecode/IL. And ASP.NET uses dynamic typing too – Panagiotis Kanavos May 18 '17 at 11:21
  • 1
    @PanagiotisKanavos benchmarks like these really! https://www.techempower.com/benchmarks/#section=data-r14&hw=ph&test=update - again I did not want to start a debate over this, I am getting along with node.js just fine during my spare time and have no plans to quit .net dev job. as some people suggested in twitter - yes I am a dark matter developer but have no ties to microsoft or any other large corporations. :) – undefined May 18 '17 at 11:41
  • @IKnowNothingAtAll make sure you know what you measure. For example, you posted a current benchmark that shows the opposite results - and you have an *older* version of Core. Who's wrong? .NET Core removes all of the infrastructure and pipeline that combine IIS and ASP.NET. *Your* code doesn't do anything, so that difference, and the runtime differences become obvious. *Data updates* on the other hand do stuff, and .NET Core doesn't do well there - it's about 5% slower, but that may well be due to the db driver. Or not – Panagiotis Kanavos May 18 '17 at 12:38
  • 1
    @IKnowNothingAtAll Is .NET core running single threaded like node does? If not then your benchmark is invalid. You have to use the cluster module like Marco Talento said in order to run across multiple CPUs. – Tony May 18 '17 at 12:46
  • @IKnowNothingAtAll but that's it - in the "actually do something" category .NET Core isn't fast enough yet, and they know it. That's what makes Data Updates an *interesting* benchmark! It also means that they've done a good job slimming the pipeline. What's really strange though is that raw DB access is slower than "middleware", even by 0.1%! WTH? Going to check the source ... – Panagiotis Kanavos May 18 '17 at 14:14
  • 3
    @Tony using cluster module NodeJs spawns multiple workers doing and share the load of main process which is listening on single process. It just avoids having to set up multiple application on different ports. Also if nodeJs is running in cluster mode then there should same number of Asp.Net WebApplications running in IIS on diff ports and share load between them through some load balancer, then it will be right comparision. – Vipresh May 18 '17 at 14:39
  • FYI, expressJS is faster when the NODE_ENV environment variable is set to production. Did you set that variable? – TerribleDev May 18 '17 at 22:35
  • As an aspnet developmer you should know dotnet full framework + iis is also mega slow. Dotnet core on the other hand was build to perform. And for nodje you are including Express is framework instead you should just return hello world to be more fair. Razor is also compiled on the fly to an internal string/class. – Joel Harkes May 19 '17 at 06:01
  • 1
    Another suggestion: `npm install -g recluster-cli && NODE_ENV=production recluster-cli yourapp.js numberofcoresonyourmachine` – Gjorgi Kjosev May 19 '17 at 09:57
  • 41
    Node.js is great for lots of things, but raw speed per request isn't one of them. What it excels at is being a broker for I/O operations, because of the non-blocking event-loop thing, which, when Node was new and shiny, was a big deal. Of course, since then other languages and frameworks have caught up, so in .NET we have the Task Parallel Library and asynchronous I/O and async/await. What Node does not excel at is CPU-bound operations like page rendering, because it's single-threaded JavaScript. – Mark Rendle May 19 '17 at 10:18
  • @PanagiotisKanavos I just opened a PR on the TechEmpower DataUpdate benchmark to fix the RawDb code, which for some reason is calling `cmd.Prepare()` on every execution of a command, instead of just once when the command is created. – Mark Rendle May 19 '17 at 10:21
  • @GorgiKosev Tried that, Req/Sec is now 1,228.80 - good improvement. I noticed the CPU usage was constantly at 100%, not sure how `recluster-cli ` manage this, gonna study this a bit in details. thanks for your suggestion. – undefined May 19 '17 at 10:38
  • Strange, I'm still seeing much smaller differences. Code: https://github.com/spion/express-vs-dotnetcore - I get 25000 req/s from node and 35000 req/s from dotnet-core in release mode with no string builder (just hello world message). This is on mac OS, not sure if it makes a big difference. – Gjorgi Kjosev May 19 '17 at 11:41
  • Thanks @GorgiKosev - With your code, in my machine (Windows 10) dnc: http://imgur.com/a/FpOp0 node: http://imgur.com/a/QCacD ... still the difference is huge, I'm using WebSurge btw. Will check on macOS and ubuntu at home tonight. – undefined May 19 '17 at 12:03
  • Yeah that seems low. I updated the github project README to include commands used and results on my machine: https://github.com/spion/express-vs-dotnetcore/blob/master/README.md – Gjorgi Kjosev May 19 '17 at 12:30
  • Any new results from wrk on mac/linux? – Gjorgi Kjosev May 22 '17 at 00:40
  • p.s. I just had a chance to test with linux and I'm getting 34000 req/s from node, 42000 req/s from dotnet core - a bit closer compared to the macOS results. – Gjorgi Kjosev Jul 08 '17 at 08:10
  • @GorgiKosev tried this test on linux (https://github.com/spion/express-vs-dotnetcore): latest node.js with turbofan is 1.5 - 3 times faster than .Net Core 2.0 depending on conditions: connections, test time and threads. – Aleksey Kontsevich Oct 31 '17 at 09:51
  • What is the impact of ResponseCaching attribute on the aspnetcore version? – rasharasha Sep 03 '19 at 21:39

2 Answers2

210

As many others have alluded, the comparison lacks context.
At the time of its release, the async approach of node.js was revolutionary. Since then other languages and web frameworks have been adopting the approaches they took mainstream.

To understand what the difference meant, you need to simulate a blocking request that represents some IO workload, such as a database request. In a thread-per-request system, this will exhaust the threadpool and new requests will be put in to a queue waiting for an available thread.
With non-blocking-io frameworks this does not happen.

Consider this node.js server that waits 1 second before responding

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

Now let's throw 100 concurrent conenctions at it, for 10s. So we expect about 1000 requests to complete.

$ 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

As you can see we get in the ballpark with 922 completed.

Now consider the following asp.net code, written as though async/await were not supported yet, therefore dating us back to the node.js launch era.

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! Here we see the limit of the threadpool. By tuning it up we could get more concurrent requests happening, but at the cost of more server resources.

For these IO-bound workloads, the move to avoid blocking the processing threads was that dramatic.

Now let's bring it to today, where that influence has rippled through the industry and allow dotnet to take advantage of its improvements.

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

No surprises here, we now match node.js.

So what does all this mean?

Your impressions that node.js is the "fastest" come from an era we are no longer living in. Add to that it was never node/js/v8 that were "fast", it was that they broke the thread-per-request model. Everyone else has been catching up.

If your goal is the fastest possible processing of single requests, then look at the serious benchmarks instead of rolling your own. But if instead what you want is simply something that scales to modern standards, then go for whichever language you like and make sure you are not blocking those threads.

Disclaimer: All code written, and tests run, on an ageing MacBook Air during a sleepy Sunday morning. Feel free to grab the code and try it on Windows or tweak to your needs - https://github.com/csainty/nodejs-vs-aspnetcore

Alex from Jitbit
  • 53,710
  • 19
  • 160
  • 149
Chris Sainty
  • 9,086
  • 1
  • 26
  • 31
  • 42
    NodeJs was never unique , the Thread per request model also existed in Asp.Net before nodejs was introduced .All the methods that did I/O had 2 versions synchronous and Asynchronous provided by the Framework ,their ASYNC methods ending with keyword "Async" for eg. methodNameAsync – Vipresh May 22 '17 at 10:50
  • As an eg. U can refer to this article related to DB operations dating back to 2008 http://www.codedigest.com/Articles/ADO/173_ADONet_20_Features_-_Asynchronous_SqlCommand_Execution.aspx – Vipresh May 22 '17 at 10:53
  • 6
    "the approaches they took mainstream" - few things are unique, they put the issue in front of a much wider audience. Having an approach available, and having it baked in as a core principle are two very different things. – Chris Sainty May 22 '17 at 13:52
  • 4
    The best answer here. Period. – Narvalex Jul 07 '17 at 14:14
  • I have been looking into benchmarks for .NET Core compared to Node.js and have found this: https://www.techempower.com/benchmarks/ On many instances Node.js performed much higher, but not in all things. I would be anxious for when Core 2 comes out for better performance. I have not started using Node yet, just researching and wanted to share. – KeyOfJ Aug 01 '17 at 19:21
  • TO be fair, for this test / example to provide any context, you're going to need to supply information on the machine running the test. You're only running one instance of the node.js app, a fair comparison would be utilising the cluster module, 1 node app instance for each core. – Lee Brindley Aug 23 '18 at 12:52
  • 3
    @LeeBrindley I disagree, this isn't trying to demonstrate maximum throughput of the given hardware, it is demonstrating the difference between blocking and non-blocking. If you want raw throughput comparisons I link to techempower. – Chris Sainty Aug 24 '18 at 13:44
  • great detailed answer. Thank you. the benchmark link you added suggests that rust based web servers are lading the way in requests handling speed! the gap was a huge surprise to me – CME64 Oct 30 '18 at 21:00
13

Node Frameworks like Express and Koa have a terrible overhead. "Raw" Node is significantly faster.

I haven't tried it, but there's a newer framework that gets very close to "Raw" Node performance: https://github.com/aerojs/aero

(see benchmark on that page)

update: Here are some figures: 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

As you can see, the overheads in the most popular node.js frameworks are VERY significant!

smerg
  • 1,506
  • 3
  • 10
  • 14