ASP.NET Core MVC vs. WCF

はじめに

以前 WCF と gRPC のベンチマークを比較して、結果 gRPC の圧勝だった。

tnakamura.hatenablog.com

ASP.NET Core MVC(Web API) と WCF ではどっちが速いか気になったので試してみた。WCF のサービスは今のところ .NET Framework で動かすしかないので、公平のために ASP.NET Core MVC .NET Framework で動かした。

ASP.NET Core MVC で Web API を作成

gRPC のときと同様に、書籍の一覧を取得する API を用意。

using Microsoft.AspNetCore;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using System;
using System.Collections.Generic;
using System.Linq;

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

        public void Configure(IApplicationBuilder app, IHostingEnvironment env)
        {
            app.UseMvc();
        }
    }

    public class Book
    {
        public string Id { get; set; }

        public string Title { get; set; }

        public string Description { get; set; }

        public string Author { get; set; }

        public int Price { get; set; }

        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<IEnumerable<Book>> Get()
        {
            return books;
        }
    }
}

WCF のサービスを作成

WCF の方でも書籍の一覧を取得するオペレーションを用意した。WCF を使うとしたら TCP か名前付きパイプくらいなので、今回はバインディングTCP を選択。

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;
        }
    }

}

ベンチマークを作成

BenchmarnDotNet を使ってベンチマークを作成。WCFASP.NET Core MVC で公平にするため、ASP.NET Core MVC 側は取得したレスポンスをデシリアライズするところまでを計測対象にした。

using BenchmarkDotNet.Attributes;
using BenchmarkDotNet.Running;
using Newtonsoft.Json;
using System;
using System.IO;
using System.Threading.Tasks;
using System.Collections.Generic;
using System.Net.Http;
using System.Runtime.Serialization;
using System.ServiceModel;

namespace AspNetCoreVsWcf.Benchmark
{
    class Program
    {
        static void Main(string[] args)
        {
            BenchmarkRunner.Run<AspNetCoreVsWcfBenchmark>();

            Console.ReadLine();
        }
    }

    [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 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 IEnumerable<Book> AspNetCore()
        {
            //var json = httpClient.GetStringAsync("http://localhost:8000/api/books")
            //    .GetAwaiter().GetResult(); ;
            //return JsonConvert.DeserializeObject<IEnumerable<Book>>(json);
            return GetBooksAsync().GetAwaiter().GetResult();
        }

        async Task<IEnumerable<Book>> GetBooksAsync()
        {
            var response = await httpClient.GetAsync("http://localhost:8000/api/books");
            var stream = await response.Content.ReadAsStreamAsync();
            using (var streamReader = new StreamReader(stream))
            using (var jsonReader = new JsonTextReader(streamReader))
            {
                return jsonSerializer.Deserialize<IEnumerable<Book>>(jsonReader);
            }
        }
    }
}

ベンチマーク結果

WCF/TCP の方が断然速かった。

f:id:griefworker:20181015133719p:plain

おわりに

ASP.NET Core MVC は HTTP2 ではないし、WCF の方が速いんだろうなとは予想していた。ただ、ここまで差が開くとは思わなかったな。素の ASP.NET Core ならもっと差は縮まりそうだけど、実際の開発では ASP.NET Core MVC を使うことがほとんどだから、やっても参考にはならないか。Kestrel と HttpClient が HTTP2 に対応するので、正式版が出たらベンチマークを取り直すかもしれない。