DEV Community

Gerasimos (Makis) Maropoulos
Gerasimos (Makis) Maropoulos

Posted on • Updated on

Go vs .NET Core in terms of HTTP performance

article cover

Hello Friends!

Lately I’ve heard a lot of discussion around the new .NET Core and its performance especially on web servers.

I didn’t want to start comparing two different things, so I did patience for quite long for a more stable version.

However, this Monday, Microsoft announced the .NET Core version 2.0, so I feel ready to do it! Do you?


As we already mentioned, we will compare two identical things here, in terms of application, the expected response and the stability of their run times, so we will not try to put more things in the game like JSON or XML encoders and decoders, just a simple text message. To achieve a fair comparison we will use the MVC architecture pattern on both sides, Go and .NET Core.

Prerequisites

Go (or Golang): is a rapidly growing open source programming language designed for building simple, fast, and reliable software.

There are not lot of web frameworks for Go with MVC support but, luckily for us Iris does the job.

Iris: A fast, simple and efficient micro web framework for Go. It provides a beautifully expressive and easy to use foundation for your next website, API, or distributed app.

C#: is a general-purpose, object-oriented programming language. Its development team is led by Anders Hejlsberg.

.NET Core: Develop high performance applications in less time, on any platform.

Download Go from https://golang.org/dl and .NET Core from https://www.microsoft.com/net/core.

After you've download and install these, you will need Iris from Go’s side. Installation is very easy, just open your terminal and execute:

go get -u github.com/kataras/iris

Benchmarking

Hardware

  • Processor: Intel(R) Core(TM) i7–4710HQ CPU @ 2.50GHz 2.50GHz
  • RAM: 8.00 GB

Software

Both of the applications will just return the text “value” on request path “api/values/{id}”.

.NET Core MVC

Logo designed by Pablo Iglesias

Created using dotnet new webapi . That webapi template will generate the code for you, including the return “value” on GET method requests.

Source Code

Program.cs

using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore;
using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Logging;

namespace netcore_mvc
{
    public class Program
    {
        public static void Main(string[] args)
        {
            BuildWebHost(args).Run();
        }

        public static IWebHost BuildWebHost(string[] args) =>
            WebHost.CreateDefaultBuilder(args)
                .UseStartup<Startup>()
                .Build();
    }
}

Startup.cs

using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;

namespace netcore_mvc
{
    public class Startup
    {
        public Startup(IConfiguration configuration)
        {
            Configuration = configuration;
        }

        public IConfiguration Configuration { get; }

        // This method gets called by the runtime. Use this method to add services to the container.
        public void ConfigureServices(IServiceCollection services)
        {
            services.AddMvcCore();
        }

        // This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
        public void Configure(IApplicationBuilder app, IHostingEnvironment env)
        {
            app.UseMvc();
        }
    }
}

The actual Controllers/ValuesController.cs

using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc;

namespace netcore_mvc.Controllers
{
    // ValuesController is the equivalent
    // `ValuesController` of the Iris 8.3 mvc application.
    [Route("api/[controller]")]
    public class ValuesController : Controller
    {
        // Get handles "GET" requests to "api/values/{id}".
        [HttpGet("{id}")]
        public string Get(int id)
        {
            return "value";
        }

        // Put handles "PUT" requests to "api/values/{id}".
        [HttpPut("{id}")]
        public void Put(int id, [FromBody]string value)
        {
        }

        // Delete handles "DELETE" requests to "api/values/{id}".
        [HttpDelete("{id}")]
        public void Delete(int id)
        {
        }
    }
}

Start the .NET Core web server and run the benchmark tool

$ cd netcore-mvc
$ dotnet run -c Release
Hosting environment: Production
Content root path: C:\mygopath\src\github.com\kataras\iris\_benchmarks\netcore-mvc
Now listening on: http://localhost:5000
Application started. Press Ctrl+C to shut down..
$ bombardier -c 125 -n 5000000 http://localhost:5000/api/values/5
Bombarding http://localhost:5000/api/values/5 with 5000000 requests using 125 connections
 5000000 / 5000000 [================================================================] 100.00% 2m3s
Done!
Statistics        Avg      Stdev        Max
  Reqs/sec     40226.03    8724.30     161919
  Latency        3.09ms     1.40ms   169.12ms
  HTTP codes:
    1xx - 0, 2xx - 5000000, 3xx - 0, 4xx - 0, 5xx - 0
    others - 0
  Throughput:     8.91MB/s

Iris MVC

Logo designed by Santosh Anand

Source Code

main.go

package main

import (
    "github.com/kataras/iris"
    "github.com/kataras/iris/_benchmarks/iris-mvc/controllers"
)

func main() {
    app := iris.New()
    app.Controller("/api/values/{id}", new(controllers.ValuesController))
    app.Run(iris.Addr(":5000"))
}

controllers/values_controller.go

package controllers

import "github.com/kataras/iris/mvc"

// ValuesController is the equivalent
// `ValuesController` of the .net core 2.0 mvc application.
type ValuesController struct {
    mvc.Controller
}

// Get handles "GET" requests to "api/values/{id}".
func (vc *ValuesController) Get() {
    // id,_ := vc.Params.GetInt("id")
    vc.Ctx.WriteString("value")
}

// Put handles "PUT" requests to "api/values/{id}".
func (vc *ValuesController) Put() {}

// Delete handles "DELETE" requests to "api/values/{id}".
func (vc *ValuesController) Delete() {}

Start the Go web server and run the benchmark tool

$ cd iris-mvc
$ go run main.go
Now listening on: http://localhost:5000
Application started. Press CTRL+C to shut down.
$ bombardier -c 125 -n 5000000 http://localhost:5000/api/values/5
Bombarding http://localhost:5000/api/values/5 with 5000000 requests using 125 connections
 5000000 / 5000000 [================================================================] 100.00% 47s
Done!
Statistics        Avg      Stdev        Max
  Reqs/sec    105643.81    7687.79     122564
  Latency        1.18ms   366.55us    22.01ms
  HTTP codes:
    1xx - 0, 2xx - 5000000, 3xx - 0, 4xx - 0, 5xx - 0
    others - 0
  Throughput:    19.65MB/s

For those who understand better by images, I did print my screen too!

Click here to see these screenshots.

Summary

  • Time to complete the 5000000 requests - smaller is better.
  • Reqs/sec – bigger is better.
  • Latency – smaller is better
  • Throughput – bigger is better.
  • Memory usage – smaller is better.
  • LOC (Lines Of Code) – smaller is better.

.NET Core MVC Application, written using 86 lines of code, ran for 2 minutes and 8 seconds serving 39311.56 requests per second within 3.19ms latency in average and 229.73ms max, the memory usage of all these was ~126MB (without the dotnet host).

Iris MVC Application, written using 27 lines of code, ran for 47 seconds serving 105643.71 requests per second within 1.18ms latency in average and 22.01ms max, the memory usage of all these was ~12MB.

There is also another benchmark with templates, scroll to the bottom.

Update: 20 August 2017

As Josh Clark and Scott Hanselman”” pointed out on this status, on .NET Core Startup.cs file the line with services.AddMvc(); can be replaced with services.AddMvcCore();. I followed their helpful instructions and re-run the benchmarks. The article now contains the latest benchmark output for the .NET Core application with the change both Josh and Scott noted.

The twitter conversion: https://twitter.com/MakisMaropoulos/status/899113215895982080

For those who want to compare with the standard services.AddMvc(); you can see the old output by pressing here.

Can you stay a bit longer for one more?

Let’s run one more benchmark, spawn 1000000 requests but this time we expect HTML generated by templates via the view engine.

.NET Core MVC with Templates

Source code files for this test are quite long to be shown in this article, however you can review them at: https://github.com/kataras/iris/tree/master/_benchmarks/netcore-mvc-templates

Start the .NET Core web server and run the benchmark tool

$ cd netcore-mvc-templates
$ dotnet run -c Release
Hosting environment: Production
Content root path: C:\mygopath\src\github.com\kataras\iris\_benchmarks\netcore-mvc-templates
Now listening on: http://localhost:5000
Application started. Press Ctrl+C to shut down.
Bombarding http://localhost:5000 with 1000000 requests using 125 connections
 1000000 / 1000000 [===============================================================] 100.00% 1m20s
Done!
Statistics        Avg      Stdev        Max
  Reqs/sec     11738.60    7741.36     125887
  Latency       10.10ms    22.10ms      1.97s
  HTTP codes:
    1xx - 0, 2xx - 1000000, 3xx - 0, 4xx - 0, 5xx - 0
    others - 0
  Throughput:    89.03MB/s

Iris MVC with Templates

Source code files for this test are quite long to be shown in this article, however you can review them at: https://github.com/kataras/iris/tree/master/_benchmarks/iris-mvc-templates

Start the Go web server and run the benchmark tool

$ cd iris-mvc-templates
$ go run main.go
Now listening on: http://localhost:5000
Application started. Press CTRL+C to shut down.
Bombarding http://localhost:5000 with 1000000 requests using 125 connections
 1000000 / 1000000 [================================================================] 100.00% 37s
Done!
Statistics        Avg      Stdev        Max
  Reqs/sec     26656.76    1944.73      31188
  Latency        4.69ms     1.20ms    22.52ms
  HTTP codes:
    1xx - 0, 2xx - 1000000, 3xx - 0, 4xx - 0, 5xx - 0
    others - 0
  Throughput:   192.51MB/s

Summary

  • Time to complete the 1000000 requests - smaller is better.
  • Reqs/sec - bigger is better.
  • Latency - smaller is better
  • Memory usage - smaller is better.
  • Throughput - bigger is better.

.NET Core MVC with Templates Application ran for 1 minute and 20 seconds serving 11738.60 requests per second with 89.03MB/s within 10.10ms latency in average and 1.97s max, the memory usage of all these was ~193MB (without the dotnet host).

Iris MVC with Templates Application ran for 37 seconds serving 26656.76 requests per second with 192.51MB/s within 1.18ms latency in average and 22.52ms max, the memory usage of all these was ~17MB.

What next?

Download the example source code from there and run the same benchmarks from your machine, then come back here and share your results with the rest of us!

Thank you all for the 100% green feedback.

P.S

I'm trying to respond to all of you but please do me a huge favor and check if your question is already answered on somewhere else's question first, thank you and have fun!

Update: Monday, 21 August 2017

A lot of people reached me saying that want to see a new benchmarking article based on the .NET Core’s "lower level" Kestrel this time.
So I did, follow the below link to learn the performance difference between Kestrel and Iris, it contains a sessions storage management benchmark too!

https://medium.com/@kataras/iris-go-vs-net-core-kestrel-in-terms-of-http-performance-806195dc93d5


This article was originally posted at medium

Top comments (13)

Collapse
 
manigandham profile image
Mani Gandham • Edited

The .NET compilation mode is different from the environment mode it runs in. Environments are just names and can be set to anything, like "production" or "dev-1234", and then easily checked within the app to load up the appropriate settings (like connection strings) or enable certain features. It's a really nice built-in system for organizing configs and operation.

The compile flag for Release or Debug is used by the compiler to actually build binaries using different settings like optimizing code and including debug symbols. The .NET devs are all telling you about the differences here because you didn't understand it, and adding an arrogant paragraph about it doesn't help your case. (Edit: looks like that paragraph has been removed now).

Regardless, it's fine if you want to show off your web framework but these benchmarks are rarely useful since it's highly unlikely you have anywhere close to the functionality offered by the ASP.NET MVC framework.

Collapse
 
ksejka profile image
Karol Sejka

hey, great post but i think there's need to be more said when it comes to benchmarking. First of all you're not only benchmarking dotnet core. there's also aspnet core and json serializaition library (same with go ofc but i'm feeling like you're picking on perf focus from dotnet team). So basically you're performing "Json serialization" benchmark from techempower techempower.com/benchmarks/#sectio...
While dotnet core and aspnet core have been greatly improved json library hasn't as of right now. so if you want to measure dotnet improvement check out "Plaintext" category. Second thing is features. aspnet core is full fledged mvc framework whereas iris is argubly less feature full. That's said your benchmark methodology info is not enough (what about server gc?) so I'd rather take results from Techempower (i'm not saying go is slower). Thanks!

Collapse
 
joalcava profile image
J. Alejandro Cardona

Sorry but your benchmark says nothing, you are comparing bicicles with motorcicles. I think you'r also insulting the hole dotnet/aspnet core community with such a simplier benchmark that only can confuse people.

Collapse
 
nepooomuk profile image
Robin

"There are not lot of web frameworks for Go with MVC support but, luckily for us Iris does the job."
This is post is sponsored by the creater of Iris. LOL.

Collapse
 
rvprasad profile image
Venkatesh-Prasad Ranganath

Nice post. While performance is a key aspect, I think 27 lines vs 86 lines is also a key aspect. If the extra lines are boiler plate, then it is not an issue as they can be auto generated. If not, then they can easily add to cognition overload, productivity, and maintenance, e.g., when they are semantic rich as in case of web routing info or use of specific annotations.

Collapse
 
lukepuplett profile image
Luke Puplett

Thanks for working on these comparisons, its good to know roughly what speed ballpark a language is in when selecting a weapon of choice, though perhaps the memory usage is most interesting, $$$ practically, leaving aside dev productivity, joy of coding, etc. etc. etc.

In my experience, in a production app, too many CPU cycles are spent formatting data for message exchange. I'd love to see a comparison of JSON serializers in each language if you ever have time.

Collapse
 
auditorofdoom profile image
Fredrik Olsson

That's awesome!

Collapse
 
kataras profile image
Gerasimos (Makis) Maropoulos

Thank you for the feedback!

Collapse
 
n1try profile image
Ferdinand Mütsch

Cool post ;) But are you sure you want to compare against Iris? Check out this discussion: reddit.com/r/golang/comments/57w79...

Collapse
 
galdin profile image
Galdin Raphael

This post has been featured in the Visual Studio Magazine 🎉

Collapse
 
lexlohr profile image
Alex Lohr

I think it's not entirely fair to compare a 100% compiled language like Go to one that runs on a JIT compiler like C#/.NET - can you compare it to something else like rust/iron, for example?

Collapse
 
maxart2501 profile image
Massimo Artizzu

That's a good point, but in the end it depends on what we want to achieve. If the goal is to just serve a small MVC application, we'd rather look at parameters like latency, number of served request, memory consumption and so on.

C#, like Java, leads with a wide margin in other context, like having solid frameworks for scalable applications. Giants use C# and won't stop any time soon. Even smaller realities like StackExchange use C#.

This test makes evident that you should choose the right tools for every task.

Collapse
 
iwanttocomment profile image
Calvin

please do this again but updated to the latest of each lang