はじめに
以前 ASP.NET Core MVC と WCF のベンチマークを比較したことがあって、
そのときは WCF の方がかなり速いなぁ、という結果だった。
ASP.NET Core MVC が思いのほか遅いのが気になったけど、それに関しては当時調べず。
tnakamura.hatenablog.com
このときのベンチマークで ASP.NET Core MVC が遅かった原因が、コンソールにログを出力していたせいだと判明。試しに Information 以下のログを出力しないように構成したら、だいぶ結果が変わったので、ベンチマークを比較し直すことにした。
ただやり直すだけでは面白くないので、ASP.NET Core MVC の方はシリアライザに ProtocolBuffers を使った場合のベンチマークも追加してみた。
ProtocolBuffers を使っても、NetTcpBinding を使った WCF には敵わなかったのが残念。
WebApiContrib.Core.Formatter.Protobuf を導入することで、シリアライザに ProtocolBuffers が使えるようになる。
using System;
using System.Linq;
using Microsoft.AspNetCore;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using ProtoBuf;
using WebApiContrib.Core.Formatter.Protobuf;
namespace AspNetCoreVsWcf.AspNetCore
{
public class Program
{
public static void Main(string[] args)
{
CreateWebHostBuilder(args).Build().Run();
}
public static IWebHostBuilder CreateWebHostBuilder(string[] args) =>
WebHost.CreateDefaultBuilder(args)
.UseStartup<Startup>()
.ConfigureLogging(logging =>
{
logging.SetMinimumLevel(LogLevel.Warning);
})
.UseUrls("http://localhost:8000");
}
public class Startup
{
public Startup(IConfiguration configuration)
{
Configuration = configuration;
}
public IConfiguration Configuration { get; }
public void ConfigureServices(IServiceCollection services)
{
services.AddMvc()
.SetCompatibilityVersion(CompatibilityVersion.Version_2_2)
.AddProtobufFormatters();
}
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
app.UseMvc();
}
}
[ProtoContract]
public class Book
{
[ProtoMember(1)]
public string Id { get; set; }
[ProtoMember(2)]
public string Title { get; set; }
[ProtoMember(3)]
public string Description { get; set; }
[ProtoMember(4)]
public string Author { get; set; }
[ProtoMember(5)]
public int Price { get; set; }
[ProtoMember(6)]
public string PublishedAt { get; set; }
}
[Route("api/[controller]")]
[ApiController]
public class BooksController : ControllerBase
{
static readonly Book[] books;
static BooksController()
{
books = Enumerable.Range(0, 50)
.Select(i => new Book()
{
Id = Guid.NewGuid().ToString(),
Title = $"Book{i}",
Author = $"Author{i}",
Description = $"Description{i}",
PublishedAt = DateTime.Today.ToString(),
Price = 2000,
})
.ToArray();
}
[HttpGet]
public ActionResult<Book[]> Get()
{
return books;
}
}
}
using System;
using System.Linq;
using System.Runtime.Serialization;
using System.ServiceModel;
namespace AspNetCoreVsWcf.Wcf
{
class Program
{
static void Main(string[] args)
{
var host = new ServiceHost(typeof(BookService));
host.AddServiceEndpoint(
typeof(IBookService),
new NetTcpBinding(),
"net.tcp://localhost:8001/BookService");
host.Open();
Console.WriteLine("Enter で終了");
Console.ReadLine();
host.Close();
}
}
[DataContract]
public class Book
{
[DataMember]
public string Id { get; set; }
[DataMember]
public string Title { get; set; }
[DataMember]
public string Description { get; set; }
[DataMember]
public string Author { get; set; }
[DataMember]
public int Price { get; set; }
[DataMember]
public string PublishedAt { get; set; }
}
[ServiceContract]
public interface IBookService
{
[OperationContract]
Book[] GetBooks();
}
public class BookService : IBookService
{
static readonly Book[] books;
static BookService()
{
books = Enumerable.Range(0, 50)
.Select(i => new Book()
{
Id = Guid.NewGuid().ToString(),
Title = $"Book{i}",
Author = $"Author{i}",
Description = $"Description{i}",
PublishedAt = DateTime.Today.ToString(),
Price = 2000,
})
.ToArray();
}
public Book[] GetBooks()
{
return books;
}
}
}
using System;
using System.IO;
using System.Net.Http;
using System.Net.Http.Headers;
using System.Runtime.Serialization;
using System.ServiceModel;
using System.Threading.Tasks;
using BenchmarkDotNet.Attributes;
using BenchmarkDotNet.Running;
using Newtonsoft.Json;
using ProtoBuf;
namespace AspNetCoreVsWcf.Benchmark
{
class Program
{
static void Main(string[] args)
{
BenchmarkRunner.Run<AspNetCoreVsWcfBenchmark>();
Console.ReadLine();
}
}
[ProtoContract]
[DataContract]
public class Book
{
[DataMember]
[ProtoMember(1)]
public string Id { get; set; }
[DataMember]
[ProtoMember(2)]
public string Title { get; set; }
[DataMember]
[ProtoMember(3)]
public string Description { get; set; }
[DataMember]
[ProtoMember(4)]
public string Author { get; set; }
[DataMember]
[ProtoMember(5)]
public int Price { get; set; }
[DataMember]
[ProtoMember(6)]
public string PublishedAt { get; set; }
}
[ServiceContract]
public interface IBookService
{
[OperationContract]
Book[] GetBooks();
}
public class AspNetCoreVsWcfBenchmark
{
IBookService tcpChannel;
HttpClient httpClient;
JsonSerializer jsonSerializer;
[GlobalSetup]
public void GlobalSetup()
{
tcpChannel = ChannelFactory<IBookService>.CreateChannel(
new NetTcpBinding(),
new EndpointAddress("net.tcp://localhost:8001/BookService"));
httpClient = new HttpClient();
jsonSerializer = JsonSerializer.CreateDefault();
}
[GlobalCleanup]
public void GlobalCleanup()
{
((IClientChannel)tcpChannel).Close();
httpClient.Dispose();
}
[Benchmark]
public Book[] WcfTcp()
{
return tcpChannel.GetBooks();
}
[Benchmark]
public async Task<Book[]> AspNetCoreJson()
{
var request = new HttpRequestMessage(
HttpMethod.Get,
"http://localhost:8000/api/books");
request.Headers.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
var response = await httpClient.SendAsync(request);
var stream = await response.Content.ReadAsStreamAsync();
using (var streamReader = new StreamReader(stream))
using (var jsonReader = new JsonTextReader(streamReader))
{
return jsonSerializer.Deserialize<Book[]>(jsonReader);
}
}
[Benchmark]
public async Task<Book[]> AspNetCoreProtobuf()
{
var request = new HttpRequestMessage(
HttpMethod.Get,
"http://localhost:8000/api/books");
request.Headers.Accept.Add(new MediaTypeWithQualityHeaderValue("application/x-protobuf"));
var response = await httpClient.SendAsync(request);
var stream = await response.Content.ReadAsStreamAsync();
return Serializer.Deserialize<Book[]>(stream);
}
}
}