論HTTP性能,Go與.NET Core一爭雌雄
朋友們,你們好!
近來,我聽到了大量的關(guān)于新出的 .NET Core 和其性能的討論,尤其在 Web 服務(wù)方面的討論更甚。
因為是新出的,我不想立馬就比較兩個不同的東西,所以我耐心等待,想等發(fā)布更穩(wěn)定的版本后再進行。
本周一(8 月 14 日),微軟發(fā)布 .NET Core 2.0 版本,因此,我準備開始。您們認為呢?
如前面所提的,我們會比較它們相同的東西,比如應(yīng)用程序、預(yù)期響應(yīng)及運行時的穩(wěn)定性,所以我們不會把像對 JSON 或者 XML 的編碼、解碼這些煩多的事情加入比較游戲中來,僅僅只會使用簡單的文本消息。為了公平起見,我們會分別使用 Go 和 .NET Core 的 MVC 架構(gòu)模式。
參賽選手
Go (或稱 Golang): 是一種快速增長的開源編程語言,旨在構(gòu)建出簡單、快捷和穩(wěn)定可靠的應(yīng)用軟件。
用于支持 Go 語言的 MVC web 框架并不多,還好我們找到了 Iris ,可勝任此工作。
Iris: 支持 Go 語言的快速、簡單和高效的微型 Web 框架。它為您的下一代網(wǎng)站、API 或分布式應(yīng)用程序奠定了精美的表現(xiàn)方式和易于使用的基礎(chǔ)。
C#: 是一種通用的、面向?qū)ο蟮木幊陶Z言。其開發(fā)團隊由 Anders Hejlsberg 領(lǐng)導(dǎo)。
.NET Core: 跨平臺,可以在極少時間內(nèi)開發(fā)出高性能的應(yīng)用程序。
可從 https://golang.org/dl 下載 Go ,從 https://www.microsoft.com/net/core 下載 .NET Core。
在下載和安裝好這些軟件后,還需要為 Go 安裝 Iris。安裝很簡單,僅僅只需要打開終端,然后執(zhí)行如下語句:
- go get -u github.com/kataras/iris
基準
硬件
- 處理器: Intel(R) Core(TM) i7–4710HQ CPU @ 2.50GHz 2.50GHz
- 內(nèi)存: 8.00 GB
軟件
- 操作系統(tǒng): 微軟 Windows [10.0.15063 版本], 電源計劃設(shè)置為“高性能”
- HTTP 基準工具: https://github.com/codesenberg/bombardier, 使用***的 1.1 版本。
- .NET Core: https://www.microsoft.com/net/core, 使用***的 2.0 版本。
- Iris: https://github.com/kataras/iris, 使用基于 Go 1.8.3 構(gòu)建的*** 8.3 版本。
兩個應(yīng)用程序都通過請求路徑 “api/values/{id}” 返回文本“值”。
.NET Core MVC
Logo 由 Pablo Iglesias 設(shè)計。
可以使用 dotnet new webapi 命令創(chuàng)建項目,其 webapi 模板會為您生成代碼,代碼包含 GET 請求方法的 返回“值”。
源代碼:
- 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();
- }
- }
- 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();
- }
- }
- }
- 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)
- {
- }
- }
- }
運行 .NET Core web 服務(wù)項目:
- $ 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.
運行和定位 HTTP 基準工具:
- $ 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 由 Santosh Anand 設(shè)計。
源代碼:
- 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"), iris.WithoutVersionChecker)
- }
- 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() {}
運行 Go web 服務(wù)項目:
- $ cd iris-mvc
- $ go run main.go
- Now listening on: http://localhost:5000
- Application started. Press CTRL+C to shut down.
運行和定位 HTTP 基準工具:
- $ 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
想通過圖片來理解的人,我也把我的屏幕截屏出來了!
請點擊這兒可以看到這些屏幕快照。
總結(jié)
- 完成 5000000 個請求的時間 - 越短越好。
- 請求次數(shù)/每秒 - 越大越好。
- 等待時間 — 越短越好。
- 吞吐量 — 越大越好。
- 內(nèi)存使用 — 越小越好。
- LOC (代碼行數(shù)) — 越少越好。
.NET Core MVC 應(yīng)用程序,使用 86 行代碼,運行 2 分鐘 8 秒,每秒接納 39311.56 個請求,平均 3.19ms 等待,***時到 229.73ms,內(nèi)存使用大約為 126MB(不包括 dotnet 框架)。
Iris MVC 應(yīng)用程序,使用 27 行代碼,運行 47 秒,每秒接納 105643.71 個請求,平均 1.18ms 等待,***時到 22.01ms,內(nèi)存使用大約為 12MB。
還有另外一個模板的基準,滾動到底部。
2017 年 8 月 20 號更新
Josh Clark 和 Scott Hanselman在此 tweet 評論上指出,.NET Core Startup.cs 文件中 services.AddMvc(); 這行可以替換為 services.AddMvcCore();。我聽從他們的意見,修改代碼,重新運行基準,該文章的 .NET Core 應(yīng)用程序的基準輸出已經(jīng)修改。
@topdawgevh @shanselman 他們也在使用 AddMvc() 而不是 AddMvcCore() ...,難道都不包含中間件?
— @clarkis117
@clarkis117 @topdawgevh Cool @MakisMaropoulos @benaadams @davidfowl 我們來看看。認真學(xué)習(xí)下怎么使用更簡單的性能默認值。
— @shanselman
@shanselman @clarkis117 @topdawgevh @benaadams @davidfowl @shanselman @benaadams @davidfowl 謝謝您們的反饋意見。我已經(jīng)修改,更新了結(jié)果,沒什么不同。對其它的建議,我非常歡迎。
— @MakisMaropoulos
它有點稍微的不同但相差不大(從 8.61MB/s 到 8.91MB/s)
想要了解跟 services.AddMvc() 標準比較結(jié)果的,可以點擊這兒。
想再多了解點兒嗎?
我們再制定一個基準,產(chǎn)生 1000000 次請求,這次會通過視圖引擎由模板生成 HTML 頁面。
.NET Core MVC 使用的模板
- using System;
- namespace netcore_mvc_templates.Models
- {
- public class ErrorViewModel
- {
- public string Title { get; set; }
- public int Code { get; set; }
- }
- }
- using System;
- using System.Collections.Generic;
- using System.Diagnostics;
- using System.Linq;
- using System.Threading.Tasks;
- using Microsoft.AspNetCore.Mvc;
- using netcore_mvc_templates.Models;
- namespace netcore_mvc_templates.Controllers
- {
- public class HomeController : Controller
- {
- public IActionResult Index()
- {
- return View();
- }
- public IActionResult About()
- {
- ViewData["Message"] = "Your application description page.";
- return View();
- }
- public IActionResult Contact()
- {
- ViewData["Message"] = "Your contact page.";
- return View();
- }
- public IActionResult Error()
- {
- return View(new ErrorViewModel { Title = "Error", Code = 500});
- }
- }
- }
- 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_templates
- {
- public class Program
- {
- public static void Main(string[] args)
- {
- BuildWebHost(args).Run();
- }
- public static IWebHost BuildWebHost(string[] args) =>
- WebHost.CreateDefaultBuilder(args)
- .UseStartup<Startup>()
- .Build();
- }
- }
- 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;
- namespace netcore_mvc_templates
- {
- 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)
- {
- /* An unhandled exception was thrown by the application.
- System.InvalidOperationException: No service for type
- 'Microsoft.AspNetCore.Mvc.ViewFeatures.ITempDataDictionaryFactory' has been registered.
- Solution: Use AddMvc() instead of AddMvcCore() in Startup.cs and it will work.
- */
- // services.AddMvcCore();
- services.AddMvc();
- }
- // This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
- public void Configure(IApplicationBuilder app, IHostingEnvironment env)
- {
- app.UseStaticFiles();
- app.UseMvc(routes =>
- {
- routes.MapRoute(
- name: "default",
- template: "{controller=Home}/{action=Index}/{id?}");
- });
- }
- }
- }
- /*
- wwwroot/css
- wwwroot/images
- wwwroot/js
- wwwroot/lib
- wwwroot/favicon.ico
- Views/Shared/_Layout.cshtml
- Views/Shared/Error.cshtml
- Views/Home/About.cshtml
- Views/Home/Contact.cshtml
- Views/Home/Index.cshtml
- These files are quite long to be shown in this article but you can view them at:
- https://github.com/kataras/iris/tree/master/_benchmarks/netcore-mvc-templates
運行 .NET Core 服務(wù)項目:
- $ 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.
運行 HTTP 基準工具:
- 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 使用的模板
- package controllers
- import "github.com/kataras/iris/mvc"
- type AboutController struct{ mvc.Controller }
- func (c *AboutController) Get() {
- c.Data["Title"] = "About"
- c.Data["Message"] = "Your application description page."
- c.Tmpl = "about.html"
- }
- package controllers
- import "github.com/kataras/iris/mvc"
- type ContactController struct{ mvc.Controller }
- func (c *ContactController) Get() {
- c.Data["Title"] = "Contact"
- c.Data["Message"] = "Your contact page."
- c.Tmpl = "contact.html"
- }
- package models
- // HTTPError a silly structure to keep our error page data.
- type HTTPError struct {
- Title string
- Code int
- }
- package controllers
- import "github.com/kataras/iris/mvc"
- type IndexController struct{ mvc.Controller }
- func (c *IndexController) Get() {
- c.Data["Title"] = "Home Page"
- c.Tmpl = "index.html"
- }
- package main
- import (
- "github.com/kataras/iris/_benchmarks/iris-mvc-templates/controllers"
- "github.com/kataras/iris"
- "github.com/kataras/iris/context"
- )
- const (
- // templatesDir is the exactly the same path that .NET Core is using for its templates,
- // in order to reduce the size in the repository.
- // Change the "C\\mygopath" to your own GOPATH.
- templatesDir = "C:\\mygopath\\src\\github.com\\kataras\\iris\\_benchmarks\\netcore-mvc-templates\\wwwroot"
- )
- func main() {
- app := iris.New()
- app.Configure(configure)
- app.Controller("/", new(controllers.IndexController))
- app.Controller("/about", new(controllers.AboutController))
- app.Controller("/contact", new(controllers.ContactController))
- app.Run(iris.Addr(":5000"), iris.WithoutVersionChecker)
- }
- func configure(app *iris.Application) {
- app.RegisterView(iris.HTML("./views", ".html").Layout("shared/layout.html"))
- app.StaticWeb("/public", templatesDir)
- app.OnAnyErrorCode(onError)
- }
- type err struct {
- Title string
- Code int
- }
- func onError(ctx context.Context) {
- ctx.ViewData("", err{"Error", ctx.GetStatusCode()})
- ctx.View("shared/error.html")
- }
- /*
- ../netcore-mvc-templates/wwwroot/css
- ../netcore-mvc-templates/wwwroot/images
- ../netcore-mvc-templates/wwwroot/js
- ../netcore-mvc-templates/wwwroot/lib
- ../netcore-mvc-templates/wwwroot/favicon.ico
- views/shared/layout.html
- views/shared/error.html
- views/about.html
- views/contact.html
- views/index.html
- These files are quite long to be shown in this article but you can view them at:
- https://github.com/kataras/iris/tree/master/_benchmarks/iris-mvc-templates
- */
運行 Go 服務(wù)項目:
- $ cd iris-mvc-templates
- $ go run main.go
- Now listening on: http://localhost:5000
- Application started. Press CTRL+C to shut down.
運行 HTTP 基準工具:
- 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
總結(jié)
- 完成 1000000 個請求的時間 - 越短越好。
- 請求次數(shù)/每秒 - 越大越好。
- 等待時間 — 越短越好。
- 內(nèi)存使用 — 越小越好。
- 吞吐量 — 越大越好。
.NET Core MVC 模板應(yīng)用程序,運行 1 分鐘 20 秒,每秒接納 11738.60 個請求,同時每秒生成 89.03M 頁面,平均 10.10ms 等待,***時到 1.97s,內(nèi)存使用大約為 193MB(不包括 dotnet 框架)。
Iris MVC 模板應(yīng)用程序,運行 37 秒,每秒接納 26656.76 個請求,同時每秒生成 192.51M 頁面,平均 1.18ms 等待,***時到 22.52ms,內(nèi)存使用大約為 17MB。
接下來呢?
這里有上面所示的源代碼,請下載下來,在您本地以同樣的基準運行,然后把運行結(jié)果在這兒給大家分享。
想添加 Go 或 C# .net core WEB 服務(wù)框架到列表的朋友請向這個倉庫的 _benchmarks 目錄推送 PR。
我也需要親自感謝下 dev.to 團隊,感謝把我的這篇文章分享到他們的 Twitter 賬戶。
感謝大家真心反饋,玩得開心!
更新 : 2017 年 8 月 21 ,周一
很多人聯(lián)系我,希望看到一個基于 .NET Core 的較低級別 Kestrel 的基準測試文章。
因此我完成了,請點擊下面的鏈接來了解 Kestrel 和 Iris 之間的性能差異,它還包含一個會話存儲管理基準!