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
Answer from Chris Sainty on Stack OverflowAs 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
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!
Being FAST and handling lots of LOAD are two different things. A server that's really FAST at serving one request per second might totally croak if you send it 500 requests per second (under LOAD).
You also have to consider static (and cached) vs dynamic pages. If you're worried about static pages, then IIS is probably going to beat node because IIS uses kernel-mode caching, which means that requests which request a static page are not even going to get out of the kernel.
I'm guessing that you're looking for a comparison between ASP.NET and node. In this battle, after everything's been compiled/interpreted you're probably going to be pretty close in performance. Maybe .NET's a little FASTER or maybe node's a little FASTER, but it's probably close enough that you don't care. I'd bet on .NET, but I don't know for sure.
The place that node is really compelling is for handling LOAD. This is where the technologies really differ. ASP.NET dedicates a thread for each request from its thread pool, and once ASP.NET has exhausted all the available threads requests begin to get queued up. If you're serving "Hello World" apps like the example by @shankar, then this might not matter that much because the threads aren't going to be blocked and you're going to be able to handle a lot of requests before you run out of threads. The problem with the ASP.NET model comes when you start making I/O requests that block the thread (call to a DB, make an http request to a service, read a file from disk). These blocking requests mean that your valuable thread from the thread pool is doing nothing. The more blocking you have, the less LOAD your ASP.NET app is going to be able to serve.
To prevent this blocking, you use I/O completion ports which don't require holding a thread while you wait for a response. ASP.NET supports this, but unfortunately many of the common frameworks/libraries in .NET DON'T. For example, ADO.NET supports I/O completion ports, but Entity Framework doesn't use them. So you can build an ASP.NET app that's purely asynchronous and handles lots of load, but most people don't because it isn't as easy as building one that's synchronous, and you might not be able to use some of your favorite parts of the framework (like linq to entities) if you do.
The problem is that ASP.NET (and the .NET Framework) were created to be un-opinionated about asynchronous I/O. .NET doesn't care if you write synchronous or asynchronous code, so it's up to the developer to make this decision. Part of this is because threading and programming with asynchronous operations was thought to be "hard", and .NET wanted to make everyone happy (noobs and experts). It got even harder because .NET ended up with 3-4 different patterns for doing async. .NET 4.5 is trying to go back and retrofit the .NET framework to have an opinionated model around async IO, but it may be a while until the frameworks you care about actually support it.
The designers of node on the other hand, made an opinionated choice that ALL I/O should be async. Because of this decision, node designers were also able to make the decision that each instance of node would be single threaded to minimize thread switching, and that one thread would just execute code that had been queued. That might be a new request, it might be the callback from a DB request, it might be the callback from a http rest request you made. Node tries to maximize CPU efficiency by eliminating thread context switches. Because node made this opinionated choice that ALL I/O is asynchronous, that also means that all it's frameworks/add-ons support this choice. It's easier to write apps that are 100% async in node (because node forces you to write apps that are async).
Again, I don't have any hard numbers to prove one way or another, but I think node would win the LOAD competition for the typical web app. A highly optimized (100% async) .NET app might give the equivalent node.js app a run for it's money, but if you took an average of all the .NET and all the node apps out there, on average node probably handles more LOAD.
I did a rudimentary performance test between nodejs and IIS. IIS is about 2.5 times faster than nodejs when dishing out "hello, world!". code below.
my hardware: Dell Latitude E6510, Core i5 (dual core), 8 GB RAM, Windows 7 Enterprise 64 bit OS
node server
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>
my own benchmark program using task parallel library:
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);
}
}
}
}
and results:
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
conclusion: IIS is faster than nodejs by about 2.5 times (on Windows). This is a very rudimentary test, and by no means conclusive. But I believe this is a good starting point. Nodejs is probably faster on other web servers, on other platforms, but on Windows IIS is the winner. Developers looking to convert their ASP.NET MVC to nodejs should pause and think twice before proceeding.
Updated (5/17/2012) Tomcat (on windows) seems to beat IIS hands down, about 3 times faster than IIS in dishing out static html.
tomcat
index.html at http://localhost:8080/test/
<p>hello, world!</p>
tomcat results
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
updated conclusion: i ran the benchmark program multiple times. Tomcat appears to be the fastest server in dishing out STATIC HTML, ON WINDOWS.
Updated (5/18/2012) Previously I had 100,000 total requests with 10,000 concurrent requests. I increased it to 1,000,000 total requess and 100,000 concurrent requests. IIS comes out as the screaming winner, with Nodejs fairing the worst. I have tabularized the results below:
.
Performance of node compared to dotnet
Ask HN: Node vs. .NET Core (2024 edition)
What's the truth? - Reg. Node vs dotnet core
is Node a better choice for backend than ASP .NET?
Which is better .NET Core or Node.js?
When should you use Node.js?
Why is .NET Core so popular?
Videos
I am interested in one question, why node is not as performant as dotnet ? Node itself is written in C++ and libuv is written in C, it doesn't mean that node should be very performant ? Or is it from the v8 engine that translates javascript to machine code first ?
I see lot of blogs saying that node.js performs better because of non-blocking I/O model. But .NET core also has async model now.In-fact in my project I use dotnet core with complete async methods. I assume async-await is nothing but a non-blocking I/O model.
I couldn't find any benchmarks that says one is faster than other. Does anyone here has compared it? This & This benchmark says dotnet is much much efficient than nodejs in 2022.
What is the truth in yr.2023?