Skip to content
Open
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
50 changes: 50 additions & 0 deletions DuckDB.NET.Benchmarks/AppenderBenchmark.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
using BenchmarkDotNet.Attributes;
using DuckDB.NET.Data;

namespace DuckDB.NET.Benchmarks;

[MemoryDiagnoser]
public class AppenderBenchmark
{
private DuckDBConnection connection = null!;

[Params(1_000_000)]
public int RowCount { get; set; }

[GlobalSetup]
public void Setup()
{
connection = new DuckDBConnection("DataSource=:memory:");
connection.Open();
}

[GlobalCleanup]
public void Cleanup()
{
connection.Dispose();
}

[IterationSetup]
public void IterationSetup()
{
using var command = connection.CreateCommand();
command.CommandText = "DROP TABLE IF EXISTS bench; CREATE TABLE bench (a INTEGER, b BIGINT, c DOUBLE, d BOOLEAN);";
command.ExecuteNonQuery();
}

[Benchmark]
public void AppendRows()
{
using var appender = connection.CreateAppender("bench");

for (var i = 0; i < RowCount; i++)
{
appender.CreateRow()
.AppendValue(i)
.AppendValue((long)i)
.AppendValue((double)i)
.AppendValue(i % 2 == 0)
.EndRow();
}
}
}
34 changes: 34 additions & 0 deletions DuckDB.NET.Benchmarks/Benchmarks.csproj
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net10.0</TargetFramework>
<TargetFrameworks>net10.0</TargetFrameworks>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
<Optimize>true</Optimize>
<BuildType>Full</BuildType>
<SignAssembly>false</SignAssembly>
</PropertyGroup>

<ItemGroup>
<PackageReference Include="BenchmarkDotNet" Version="0.15.4" />
</ItemGroup>

<ItemGroup>
<ProjectReference Include="..\DuckDB.NET.Data\Data.csproj" />
<ProjectReference Include="..\DuckDB.NET.Bindings\Bindings.csproj" />
</ItemGroup>

<!-- Copy the already-downloaded native DuckDB library into the output so BenchmarkDotNet's
spawned process can load it via runtimes/{rid}/native/. -->
<ItemGroup>
<None Include="..\DuckDB.NET.Bindings\obj\runtimes\**\*.dll;..\DuckDB.NET.Bindings\obj\runtimes\**\*.so;..\DuckDB.NET.Bindings\obj\runtimes\**\*.dylib;">
<Visible>false</Visible>
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
<Link>runtimes\%(RecursiveDir)\%(FileName)%(Extension)</Link>
</None>
</ItemGroup>

</Project>
45 changes: 45 additions & 0 deletions DuckDB.NET.Benchmarks/NativeLibraryLoader.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;

namespace DuckDB.NET.Benchmarks;

internal static class NativeLibraryLoader
{
[ModuleInitializer]
public static void Init()
{
if (GetRid() is not { } rid)
{
return;
}

_ = NativeLibrary.TryLoad(Path.Join("runtimes", rid, "native", "duckdb"), Assembly.GetExecutingAssembly(), DllImportSearchPath.AssemblyDirectory, out _) ||
NativeLibrary.TryLoad(Path.Join("runtimes", rid, "native", "libduckdb"), Assembly.GetExecutingAssembly(), DllImportSearchPath.AssemblyDirectory, out _);
}

private static string? GetRid()
{
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
{
return Environment.Is64BitProcess ? "win-x64" : "win-x86";
}

if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux))
{
return RuntimeInformation.ProcessArchitecture switch
{
Architecture.X64 => "linux-x64",
Architecture.Arm64 => "linux-arm64",
_ => null,
};
}

if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX))
{
return "osx";
}

return null;
}
}
15 changes: 15 additions & 0 deletions DuckDB.NET.Benchmarks/Program.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
using BenchmarkDotNet.Configs;
using BenchmarkDotNet.Jobs;
using BenchmarkDotNet.Running;
using BenchmarkDotNet.Toolchains.InProcess.Emit;
using DuckDB.NET.Benchmarks;

// The repo's Directory.Build.props renames the output assembly to DuckDB.NET.Benchmarks
// while the project file stays Benchmarks.csproj, so BenchmarkDotNet's default toolchain
// can't locate the csproj. Run in-process to avoid the separate build/spawn step.
var config = DefaultConfig.Instance
.AddJob(Job.Default.WithToolchain(InProcessEmitToolchain.Instance));

BenchmarkSwitcher
.FromTypes([typeof(AppenderBenchmark)])
.Run(args, config);
15 changes: 14 additions & 1 deletion DuckDB.NET.Data/DuckDBAppender.cs
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ public class DuckDBAppender : IDisposable
private readonly DuckDBLogicalType[] logicalTypes;
private readonly DuckDBDataChunk dataChunk;
private readonly VectorDataWriterBase[] vectorWriters;
private DuckDBAppenderRow? currentRow;

internal DuckDBAppender(Native.DuckDBAppender appender, string qualifiedTableName)
{
Expand Down Expand Up @@ -60,7 +61,19 @@ public IDuckDBAppenderRow CreateRow()
}

rowCount++;
return new DuckDBAppenderRow(qualifiedTableName, vectorWriters, rowCount - 1, dataChunk, nativeAppender);

// A single row is filled and ended before the next CreateRow() call, so reuse one instance
// instead of allocating a DuckDBAppenderRow per row.
if (currentRow is null)
{
currentRow = new DuckDBAppenderRow(qualifiedTableName, vectorWriters, rowCount - 1, dataChunk, nativeAppender);
}
else
{
currentRow.Reset(rowCount - 1);
}

return currentRow;
}

public void Clear()
Expand Down
14 changes: 13 additions & 1 deletion DuckDB.NET.Data/DuckDBAppenderRow.cs
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
private int columnIndex = 0;
private readonly string qualifiedTableName;
private readonly VectorDataWriterBase[] vectorWriters;
private readonly ulong rowIndex;
private ulong rowIndex;
private readonly DuckDBDataChunk dataChunk;
private readonly Native.DuckDBAppender nativeAppender;

Expand All @@ -21,6 +21,18 @@
this.nativeAppender = nativeAppender;
}

/// <summary>
/// Re-targets this row instance at a new row index so the appender can reuse a single
/// <see cref="DuckDBAppenderRow"/> instead of allocating one per row. The table name, vector
/// writers, data chunk and native appender are stable for the lifetime of the appender, so only
/// the row index and column cursor need to be reset.
/// </summary>
internal void Reset(ulong rowIndex)
{
this.rowIndex = rowIndex;
columnIndex = 0;
}

public void EndRow()
{
if (columnIndex < vectorWriters.Length)
Expand Down Expand Up @@ -171,7 +183,7 @@

private unsafe IDuckDBAppenderRow AppendSpan(Span<byte> val)
{
if (val == null)

Check warning on line 186 in DuckDB.NET.Data/DuckDBAppenderRow.cs

View workflow job for this annotation

GitHub Actions / Build library (macos-14)

Comparing a span to 'null' might be redundant, the 'null' literal will be implicitly converted to a 'Span<T>.Empty' (https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca2265)

Check warning on line 186 in DuckDB.NET.Data/DuckDBAppenderRow.cs

View workflow job for this annotation

GitHub Actions / Build library (macos-14)

Comparing a span to 'null' might be redundant, the 'null' literal will be implicitly converted to a 'Span<T>.Empty' (https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca2265)

Check warning on line 186 in DuckDB.NET.Data/DuckDBAppenderRow.cs

View workflow job for this annotation

GitHub Actions / Build library (ubuntu-latest)

Comparing a span to 'null' might be redundant, the 'null' literal will be implicitly converted to a 'Span<T>.Empty' (https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca2265)

Check warning on line 186 in DuckDB.NET.Data/DuckDBAppenderRow.cs

View workflow job for this annotation

GitHub Actions / Build library (ubuntu-latest)

Comparing a span to 'null' might be redundant, the 'null' literal will be implicitly converted to a 'Span<T>.Empty' (https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca2265)

Check warning on line 186 in DuckDB.NET.Data/DuckDBAppenderRow.cs

View workflow job for this annotation

GitHub Actions / Build library (windows-latest)

Comparing a span to 'null' might be redundant, the 'null' literal will be implicitly converted to a 'Span<T>.Empty' (https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca2265)

Check warning on line 186 in DuckDB.NET.Data/DuckDBAppenderRow.cs

View workflow job for this annotation

GitHub Actions / Build library (windows-latest)

Comparing a span to 'null' might be redundant, the 'null' literal will be implicitly converted to a 'Span<T>.Empty' (https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca2265)
{
return AppendNullValue();
}
Expand Down
1 change: 1 addition & 0 deletions DuckDB.NET.slnx
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
<Solution>
<Project Path="DuckDB.NET.Benchmarks/Benchmarks.csproj" />
<Project Path="DuckDB.NET.Bindings/Bindings.csproj" />
<Project Path="DuckDB.NET.Data/Data.csproj" />
<Project Path="DuckDB.NET.Samples/Samples.csproj" />
Expand Down
Loading