1
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

.NET 8 + ASP.NET Core + EFCoreでどれだけ起動が早くなったか確認する

Last updated at Posted at 2023-09-22

はじめに

以前この記事で.NET + EFCoreは遅いという記事を見てからずっとやろうと思っていたんですが、2つ前の記事で.NET 8.0 RC1のDevContainerを作ったので、.NET 8.0 RC1でどうなったか確認してみました。

サンプルコード

今回は.NET 7.0, .NET 8.0 RC1, .NET 8.0 RC1 AOTで下記のパターンを5回計測しました。

  • データアクセス無し(単に現在時刻を返すだけ)
  • EFCoreで単一テーブルを主キーで検索
  • Dapperで単一テーブルを主キーで検索
  • ADO.NETで単一テーブルを主キーで検索

ソースは以下のようになっています。
ソースコードはここからで確認できます。
ただし、.NET 8.0 RC1 AOTではEFCoreやDapperは実行時に異常終了したり、そもそももとにしているHostBuilderも違うのでGitHubのソースコードを確認してください。

Program.cs
using Dapper;
using Microsoft.EntityFrameworkCore;
using MySqlConnector;
using System.ComponentModel.DataAnnotations.Schema;
using System.Data;

var builder = WebApplication.CreateBuilder(args);
var connectionString = builder.Configuration.GetConnectionString("db");
builder.Services.AddScoped<IDbConnection>(_ => new MySqlConnection(connectionString));
builder.Services.AddDbContext<ItemContext>(opt =>
{
    opt.UseMySql(connectionString, ServerVersion.Parse("8.0"))
       .UseQueryTrackingBehavior(QueryTrackingBehavior.NoTracking);
});

var app = builder.Build();
app.Map("/", () => $"aspnet{System.Runtime.InteropServices.RuntimeInformation.FrameworkDescription}-{DateTimeOffset.UtcNow.ToUnixTimeMilliseconds()}");
app.MapPost("/find", async (ItemContext db, FindItem input) =>
    await db.Items.Where(r => r.Price >= input.Price).OrderBy(r => r.Price).ToArrayAsync()
);
app.MapPost("/findQuery", async (IDbConnection db, FindItem input) =>
    (await db.QueryAsync<Item>("select id, price from items where price = @price order by price", new { price = input.Price })).ToArray()
);
app.MapPost("/findQueryRaw", (IDbConnection db, FindItem input) =>
{
    if (db.State != ConnectionState.Open)
        db.Open();
    using var command = db.CreateCommand();
    command.CommandText = $"select id, price from items where price = {input.Price} order by price";
    using var reader = command.ExecuteReader();
    var result = new List<Item>();
    while (reader.Read())
    {
        result.Add(new Item
        {
            Id = reader.GetString(0),
            Price = reader.GetInt32(1)
        });
    }
    return result.ToArray();
});

app.Run();

record FindItem(int Price);

[Table("items")]
public class Item
{
    [Column("id")]
    public required string Id { get; set; }

    [Column("price")]
    public int Price { get; set; }
}

public class ItemContext : DbContext
{
    public DbSet<Item> Items => Set<Item>();

    public ItemContext(DbContextOptions<ItemContext> opts) : base(opts) { }
}

計測結果

.NET 8.0 RC1は現時点では.NET 7.0よりも遅いですね。これは現時点ではチューニングの最終段階が完了していないのとEFCoreもミドルウェアの関係上7.0を利用しているためだと思います。
image.png
ざっくり見ていくと、素のASP.NETではAOTがかなり効きますね。
初回アクセスが2回目以降と変わりがないです。
image.png
EFCore、Dapper、素のADO.NETで比べると、やっぱりEFCoreの遅さが目立ちますね。

初回アクセス時にEFCoreは

  • Dapperに比べて2.5倍遅い
  • 素のADO.NETに比べ3.5倍遅い

2回目以降のアクセス時にEFCoreは

  • Dapperに比べて2.5倍遅い
  • 素のADO.NETに比べ2.5倍遅い

image.png
AOTするとリフレクションなどの動的なコード生成が失敗するので、現時点ではEFCoreやDapperは実行時に異常終了します。ADO.NETを素で使った場合だけ計測できたので載せています。こちらも初回アクセスが早くなっていますね。
image.png

おわりに

ということで、タイトルの答えとしては.NET 8.0 RC1の段階では.NET 7.0からそう変わらないという状況になりました。ただし、RC1から製品になるのと、ミドルウェアがEFCore 8.0に対応すると変わってくるかもしれません。

1
2
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?