Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

npgsql batch insert benchmark #77

Merged
merged 1 commit into from
Nov 16, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion Directory.Packages.props
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
<PackageVersion Include="BenchmarkDotNet" Version="0.13.10" />
<PackageVersion Include="FastMember" Version="1.5.0" />
<PackageVersion Include="Dapper" Version="2.1.21" />
<PackageVersion Include="Dapper.AOT" Version="0.5.0-beta.139" />
<PackageVersion Include="Dapper.AOT" Version="1.0.14" />
<PackageVersion Include="Dapper.StrongName" Version="2.1.21" />
<PackageVersion Include="Nerdbank.GitVersioning" Version="3.6.133" />
<PackageVersion Include="Npgsql" Version="7.0.6" />
Expand Down
137 changes: 83 additions & 54 deletions test/UsageBenchmark/BatchInsertBenchmarks.cs
Original file line number Diff line number Diff line change
@@ -1,44 +1,62 @@
using BenchmarkDotNet.Attributes;
using BenchmarkDotNet.Configs;
using Microsoft.Data.SqlClient;
using Npgsql;
using System;
using System.Data;
using System.Data.Common;
using System.Threading;
using System.Threading.Tasks;
using Testcontainers.PostgreSql;
using UsageBenchmark;

namespace Dapper;

[ShortRunJob, MemoryDiagnoser, GroupBenchmarksBy(BenchmarkLogicalGroupRule.ByCategory), CategoriesColumn]
public class BatchInsertBenchmarks : IDisposable
public class BatchInsertBenchmarks : IAsyncDisposable
{
private readonly SqlConnection connection = new(Program.ConnectionString);
private readonly SqlConnection sqlClient = new(Program.ConnectionString);
private readonly NpgsqlConnection npgsql = new();

private readonly PostgreSqlContainer _postgresContainer = new PostgreSqlBuilder()
.WithImage("postgres:15-alpine")
.Build();

private Customer[] customers = [];

public void DebugState()
{
// waiting on binaries from https://github.com/dotnet/SqlClient/pull/1825
Console.WriteLine($"{nameof(connection.CanCreateBatch)}: {connection.CanCreateBatch}");
if (connection.CanCreateBatch)
Console.WriteLine($"{nameof(sqlClient.CanCreateBatch)}: {sqlClient.CanCreateBatch}");
if (sqlClient.CanCreateBatch)
{
using var batch = connection.CreateBatch();
using var batch = sqlClient.CreateBatch();
var cmd = batch.CreateBatchCommand();
Console.WriteLine($"{nameof(cmd.CanCreateParameter)}: {cmd.CanCreateParameter}");
}
}

public BatchInsertBenchmarks()
{
try { connection.Execute("drop table BenchmarkCustomers;"); } catch { }
connection.Execute("create table BenchmarkCustomers(Id int not null identity(1,1), Name nvarchar(200) not null);");
try { sqlClient.Execute("drop table BenchmarkCustomers;"); } catch { }
sqlClient.Execute("create table BenchmarkCustomers(Id int not null identity(1,1), Name nvarchar(200) not null);");

_postgresContainer.StartAsync().GetAwaiter().GetResult(); // yes, I know
npgsql.ConnectionString = _postgresContainer.GetConnectionString();

npgsql.Execute("""
CREATE TABLE IF NOT EXISTS BenchmarkCustomers(
Id integer GENERATED ALWAYS AS IDENTITY,
Name varchar(40) NOT NULL
);
""");
}

[Params(0, 1, 10, 100, 1000)]
public int Count { get; set; }

[Params(false, true)]
public bool IsOpen { get; set; }
public bool IsOpen { get; set; } = true;

[GlobalSetup]
public void Setup()
Expand All @@ -51,42 +69,45 @@ public void Setup()
customers = arr;
if (IsOpen)
{
connection.Open();
sqlClient.Open();
npgsql.Open();
}
else
{
connection.Close();
sqlClient.Close();
npgsql.Close();
}
connection.Execute("truncate table BenchmarkCustomers;");
sqlClient.Execute("truncate table BenchmarkCustomers;");
npgsql.Execute("TRUNCATE BenchmarkCustomers RESTART IDENTITY;");
}

[Benchmark, BenchmarkCategory("Sync")]
public int Dapper() => connection.Execute("insert BenchmarkCustomers (Name) values (@name)", customers);
[Benchmark, BenchmarkCategory("Sync", "SqlClient")]
public int Dapper() => sqlClient.Execute("insert BenchmarkCustomers (Name) values (@name)", customers);

[Benchmark, BenchmarkCategory("Sync"), DapperAot, CacheCommand]
public int DapperAot() => connection.Execute("insert BenchmarkCustomers (Name) values (@name)", customers);
[Benchmark, BenchmarkCategory("Sync", "SqlClient"), DapperAot, CacheCommand]
public int DapperAot() => sqlClient.Execute("insert BenchmarkCustomers (Name) values (@name)", customers);

[Benchmark, BenchmarkCategory("Sync")]
public int DapperAotManual() => connection.Command("insert BenchmarkCustomers (Name) values (@name)",
[Benchmark, BenchmarkCategory("Sync", "SqlClient")]
public int DapperAotManual() => sqlClient.Command("insert BenchmarkCustomers (Name) values (@name)",
handler: CustomHandler.Unprepared).Execute(customers);

[Benchmark, BenchmarkCategory("Sync")]
public int DapperAot_PreparedManual() => connection.Command("insert BenchmarkCustomers (Name) values (@name)",
[Benchmark, BenchmarkCategory("Sync", "SqlClient")]
public int DapperAot_PreparedManual() => sqlClient.Command("insert BenchmarkCustomers (Name) values (@name)",
handler: CustomHandler.Prepared).Execute(customers);

[Benchmark(Baseline = true), BenchmarkCategory("Sync")]
[Benchmark(Baseline = true), BenchmarkCategory("Sync", "SqlClient")]
public int Manual()
{
if (customers.Length == 0) return 0;
bool close = false;
try
{
if (connection.State != ConnectionState.Open)
if (sqlClient.State != ConnectionState.Open)
{
connection.Open();
sqlClient.Open();
close = true;
}
var cmd = GetManualCommand(connection, out var name);
var cmd = GetManualCommand(sqlClient, out var name);
if (customers.Length != 1) cmd.Prepare();

int total = 0;
Expand All @@ -100,10 +121,16 @@ public int Manual()
}
finally
{
if (close) connection.Close();
if (close) sqlClient.Close();
}
}

[Benchmark, BenchmarkCategory("Sync", "Npgsql"), DapperAot, CacheCommand, BatchSize(0)]
public int NpgsqlDapperAotNoBatch() => npgsql.Execute("insert into BenchmarkCustomers (Name) values (@name)", customers);

[Benchmark, BenchmarkCategory("Sync", "Npgsql"), DapperAot, CacheCommand, BatchSize(-1)]
public int NpgsqlDapperAotFullBatch() => npgsql.Execute("insert into BenchmarkCustomers (Name) values (@name)", customers);

private static DbCommand? _spare;
private static DbCommand GetManualCommand(DbConnection connection, out DbParameter name)
{
Expand Down Expand Up @@ -131,30 +158,30 @@ private static DbCommand GetManualCommand(DbConnection connection, out DbParamet
private static bool RecycleManual(DbCommand cmd)
=> Interlocked.CompareExchange(ref _spare, cmd, null) is null;

[Benchmark, BenchmarkCategory("Async")]
public Task<int> DapperAsync() => connection.ExecuteAsync("insert BenchmarkCustomers (Name) values (@name)", customers);
[Benchmark, BenchmarkCategory("Async", "SqlClient")]
public Task<int> DapperAsync() => sqlClient.ExecuteAsync("insert BenchmarkCustomers (Name) values (@name)", customers);

[Benchmark, BenchmarkCategory("Async")]
public Task<int> DapperAotAsync() => connection.Command("insert BenchmarkCustomers (Name) values (@name)",
[Benchmark, BenchmarkCategory("Async", "SqlClient")]
public Task<int> DapperAotAsync() => sqlClient.Command("insert BenchmarkCustomers (Name) values (@name)",
handler: CustomHandler.Unprepared).ExecuteAsync(customers);

[Benchmark, BenchmarkCategory("Async")]
public Task<int> DapperAot_PreparedAsync() => connection.Command("insert BenchmarkCustomers (Name) values (@name)",
[Benchmark, BenchmarkCategory("Async", "SqlClient")]
public Task<int> DapperAot_PreparedAsync() => sqlClient.Command("insert BenchmarkCustomers (Name) values (@name)",
handler: CustomHandler.Prepared).ExecuteAsync(customers);

[Benchmark(Baseline = true), BenchmarkCategory("Async")]
[Benchmark(Baseline = true), BenchmarkCategory("Async", "SqlClient")]
public async Task<int> ManualAsync()
{
if (customers.Length == 0) return 0;
bool close = false;
try
{
if (connection.State != ConnectionState.Open)
if (sqlClient.State != ConnectionState.Open)
{
await connection.OpenAsync();
await sqlClient.OpenAsync();
close = true;
}
var cmd = GetManualCommand(connection, out var name);
var cmd = GetManualCommand(sqlClient, out var name);
if (customers.Length != 1)
{
await cmd.PrepareAsync();
Expand All @@ -177,12 +204,12 @@ public async Task<int> ManualAsync()
{
if (close)
{
await connection.CloseAsync();
await sqlClient.CloseAsync();
}
}
}

[Benchmark, BenchmarkCategory("Sync")]
[Benchmark, BenchmarkCategory("Sync", "SqlClient")]
public int EntityFramework()
{
using var ctx = new MyContext();
Expand All @@ -192,7 +219,7 @@ public int EntityFramework()
return result;
}

[Benchmark, BenchmarkCategory("Async")]
[Benchmark, BenchmarkCategory("Async", "SqlClient")]
public async Task<int> EntityFrameworkAsync()
{
using var ctx = new MyContext();
Expand All @@ -202,18 +229,18 @@ public async Task<int> EntityFrameworkAsync()
return result;
}

[Benchmark, BenchmarkCategory("Sync")]
[Benchmark, BenchmarkCategory("Sync", "SqlClient")]
public int SqlBulkCopyFastMember()
{
bool close = false;
try
{
if (connection.State != ConnectionState.Open)
if (sqlClient.State != ConnectionState.Open)
{
connection.Open();
sqlClient.Open();
close = true;
}
using var table = new SqlBulkCopy(connection)
using var table = new SqlBulkCopy(sqlClient)
{
DestinationTableName = "BenchmarkCustomers",
ColumnMappings =
Expand All @@ -227,22 +254,22 @@ public int SqlBulkCopyFastMember()
}
finally
{
if (close) connection.Close();
if (close) sqlClient.Close();
}
}

[Benchmark, BenchmarkCategory("Sync")]
[Benchmark, BenchmarkCategory("Sync", "SqlClient")]
public int SqlBulkCopyDapper()
{
bool close = false;
try
{
if (connection.State != ConnectionState.Open)
if (sqlClient.State != ConnectionState.Open)
{
connection.Open();
sqlClient.Open();
close = true;
}
using var table = new SqlBulkCopy(connection)
using var table = new SqlBulkCopy(sqlClient)
{
DestinationTableName = "BenchmarkCustomers",
ColumnMappings =
Expand All @@ -256,22 +283,22 @@ public int SqlBulkCopyDapper()
}
finally
{
if (close) connection.Close();
if (close) sqlClient.Close();
}
}

[Benchmark, BenchmarkCategory("Async")]
[Benchmark, BenchmarkCategory("Async", "SqlClient")]
public async Task<int> SqlBulkCopyFastMemberAsync()
{
bool close = false;
try
{
if (connection.State != ConnectionState.Open)
if (sqlClient.State != ConnectionState.Open)
{
await connection.OpenAsync();
await sqlClient.OpenAsync();
close = true;
}
using var table = new SqlBulkCopy(connection)
using var table = new SqlBulkCopy(sqlClient)
{
DestinationTableName = "BenchmarkCustomers",
ColumnMappings =
Expand All @@ -285,13 +312,15 @@ public async Task<int> SqlBulkCopyFastMemberAsync()
}
finally
{
if (close) await connection.CloseAsync();
if (close) await sqlClient.CloseAsync();
}
}

public void Dispose()
public async ValueTask DisposeAsync()
{
connection.Dispose();
await sqlClient.DisposeAsync();
await npgsql.DisposeAsync();
await _postgresContainer.DisposeAsync();
GC.SuppressFinalize(this);
}

Expand Down
5 changes: 4 additions & 1 deletion test/UsageBenchmark/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ static class Program
#else
static async Task Main()
{
using (var obj = new BatchInsertBenchmarks())
await using (var obj = new BatchInsertBenchmarks())
{
await RunAllInserts(obj, 10, false);
await RunAllInserts(obj, 10, true);
Expand Down Expand Up @@ -54,6 +54,9 @@ static async Task RunAllInserts(BatchInsertBenchmarks obj, int count, bool isOpe
Console.WriteLine(obj.SqlBulkCopyFastMember());
Console.WriteLine(obj.SqlBulkCopyDapper());
Console.WriteLine(await obj.SqlBulkCopyFastMemberAsync());

Console.WriteLine(obj.NpgsqlDapperAotNoBatch());
Console.WriteLine(obj.NpgsqlDapperAotFullBatch());
}

static async Task RunAllQueries(QueryBenchmarks obj, int count, bool isOpen)
Expand Down
2 changes: 2 additions & 0 deletions test/UsageBenchmark/UsageBenchmark.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -18,5 +18,7 @@
<PackageReference Include="Microsoft.Data.SqlClient" />
<PackageReference Include="System.Data.SqlClient" />
<PackageReference Include="BenchmarkDotNet" />
<PackageReference Include="Testcontainers.PostgreSql" />
<PackageReference Include="Npgsql" />
</ItemGroup>
</Project>