Skip to content

Commit

Permalink
npgsql batch insert benchmark (#77)
Browse files Browse the repository at this point in the history
  • Loading branch information
mgravell authored Nov 16, 2023
1 parent 66e1e2b commit a5fba00
Show file tree
Hide file tree
Showing 4 changed files with 90 additions and 56 deletions.
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>

0 comments on commit a5fba00

Please sign in to comment.