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
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -314,3 +314,5 @@ version.txt
/docs/.docusaurus/
/docs/build/
/docs/.yarn/

BenchmarkDotNet.Artifacts/
18 changes: 18 additions & 0 deletions .run/KurrentDB Bookings.run.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
<component name="ProjectRunConfigurationManager">
<configuration default="false" name="KurrentDB Bookings" type="LaunchSettings" factoryName=".NET Launch Settings Profile">
<option name="LAUNCH_PROFILE_PROJECT_FILE_PATH" value="$PROJECT_DIR$/samples/kurrentdb/Bookings/Bookings.csproj" />
<option name="LAUNCH_PROFILE_TFM" value="net10.0" />
<option name="LAUNCH_PROFILE_NAME" value="Samples.KurrentDB.Bookings" />
<option name="USE_MONO" value="0" />
<option name="RUNTIME_ARGUMENTS" value="" />
<option name="GENERATE_APPLICATIONHOST_CONFIG" value="1" />
<option name="SHOW_IIS_EXPRESS_OUTPUT" value="0" />
<option name="SEND_DEBUG_REQUEST" value="1" />
<option name="ADDITIONAL_IIS_EXPRESS_ARGUMENTS" value="" />
<option name="AUTO_ATTACH_CHILDREN" value="0" />
<option name="MIXED_MODE_DEBUG" value="0" />
<method v="2">
<option name="Build" />
</method>
</configuration>
</component>
8 changes: 4 additions & 4 deletions Eventuous.slnx
Original file line number Diff line number Diff line change
Expand Up @@ -140,10 +140,10 @@
<Project Path="src/SqlServer/test/Eventuous.Tests.SqlServer/Eventuous.Tests.SqlServer.csproj" />
</Folder>
<Folder Name="/Samples/" />
<Folder Name="/Samples/Esdb/">
<Project Path="samples/esdb/Bookings.Domain/Bookings.Domain.csproj" />
<Project Path="samples/esdb/Bookings.Payments/Bookings.Payments.csproj" />
<Project Path="samples/esdb/Bookings/Bookings.csproj" />
<Folder Name="/Samples/KurrentDB/">
<Project Path="samples/kurrentdb/Bookings.Domain/Bookings.Domain.csproj" />
<Project Path="samples/kurrentdb/Bookings.Payments/Bookings.Payments.csproj" />
<Project Path="samples/kurrentdb/Bookings/Bookings.csproj" />
</Folder>
<Folder Name="/Samples/Postgres/">
<Project Path="samples/postgres/Bookings.Domain/Bookings.Domain.csproj" />
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
<Platforms>AnyCPU</Platforms>
<EmitCompilerGeneratedFiles>true</EmitCompilerGeneratedFiles>
<CompilerGeneratedFilesOutputPath>obj/Generated</CompilerGeneratedFilesOutputPath>
<OutputType>exe</OutputType>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="FluentValidation"/>
Expand Down
File renamed without changes.
File renamed without changes.
Original file line number Diff line number Diff line change
@@ -1,21 +1,21 @@
services:

esdb:
container_name: esdemo-esdb
image: eventstore/eventstore:23.10.2-alpha-arm64v8
# image: eventstore/eventstore:latest #23.10.2-buster-slim
ports:
- '2113:2113'
- '1113:1113'
environment:
EVENTSTORE_INSECURE: 'true'
EVENTSTORE_CLUSTER_SIZE: 1
EVENTSTORE_EXT_TCP_PORT: 1113
EVENTSTORE_HTTP_PORT: 2113
EVENTSTORE_ENABLE_EXTERNAL_TCP: 'true'
EVENTSTORE_RUN_PROJECTIONS: all
EVENTSTORE_START_STANDARD_PROJECTIONS: "true"
EVENTSTORE_ENABLE_ATOM_PUB_OVER_HTTP: "true"
# esdb:
# container_name: esdemo-esdb
# image: eventstore/eventstore:23.10.2-alpha-arm64v8
# # image: eventstore/eventstore:latest #23.10.2-buster-slim
# ports:
# - '2113:2113'
# - '1113:1113'
# environment:
# EVENTSTORE_INSECURE: 'true'
# EVENTSTORE_CLUSTER_SIZE: 1
# EVENTSTORE_EXT_TCP_PORT: 1113
# EVENTSTORE_HTTP_PORT: 2113
# EVENTSTORE_ENABLE_EXTERNAL_TCP: 'true'
# EVENTSTORE_RUN_PROJECTIONS: all
# EVENTSTORE_START_STANDARD_PROJECTIONS: "true"
# EVENTSTORE_ENABLE_ATOM_PUB_OVER_HTTP: "true"

mongo:
container_name: esdemo-mongo
Expand Down
4 changes: 2 additions & 2 deletions samples/postgres/Bookings.Domain/Bookings.Domain.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
<ProjectReference Include="$(CoreRoot)\Eventuous.Shared\Eventuous.Shared.csproj" />
</ItemGroup>
<ItemGroup>
<Compile Include="..\..\esdb\Bookings.Domain\*.cs" />
<Compile Include="..\..\esdb\Bookings.Domain\Bookings\*.cs" LinkBase="Bookings" />
<Compile Include="..\..\kurrentdb\Bookings.Domain\*.cs" />
<Compile Include="..\..\kurrentdb\Bookings.Domain\Bookings\*.cs" LinkBase="Bookings" />
</ItemGroup>
</Project>
4 changes: 2 additions & 2 deletions samples/postgres/Bookings.Payments/Bookings.Payments.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -29,8 +29,8 @@
<Compile Include="..\Bookings\Infrastructure\Telemetry.cs">
<Link>Infrastructure\Telemetry.cs</Link>
</Compile>
<Compile Include="..\..\esdb\Bookings.Payments\Domain\*.cs" LinkBase="Domain"/>
<Compile Include="..\..\esdb\Bookings.Payments\Application\*.cs" LinkBase="Application"/>
<Compile Include="..\..\kurrentdb\Bookings.Payments\Domain\*.cs" LinkBase="Domain"/>
<Compile Include="..\..\kurrentdb\Bookings.Payments\Application\*.cs" LinkBase="Application"/>
</ItemGroup>
<ItemGroup>
<ProjectReference Include="$(SrcRoot)\Diagnostics\src\Eventuous.Diagnostics.OpenTelemetry\Eventuous.Diagnostics.OpenTelemetry.csproj"/>
Expand Down
2 changes: 1 addition & 1 deletion samples/postgres/Bookings/Bookings.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,6 @@
<ProjectReference Include="$(SrcRoot)\Postgres\src\Eventuous.Postgresql\Eventuous.Postgresql.csproj"/>
<ProjectReference Include="$(SrcRoot)\RabbitMq\src\Eventuous.RabbitMq\Eventuous.RabbitMq.csproj"/>
<ProjectReference Include="$(SrcRoot)\Core\gen\Eventuous.Subscriptions.Generators\Eventuous.Subscriptions.Generators.csproj" OutputItemType="Analyzer" ReferenceOutputAssembly="false" />
<ProjectReference Include="..\Bookings.Domain\Bookings.Domain.csproj"/>
<ProjectReference Include="..\Bookings.Domain\Bookings.Domain.csproj" />
</ItemGroup>
</Project>
131 changes: 131 additions & 0 deletions src/Benchmarks/Benchmarks/AllocationHotspotsBenchmarks.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,131 @@
using BenchmarkDotNet.Attributes;
using System.Text;

namespace Benchmarks;

/// <summary>
/// Benchmarks for common allocation hotspots identified in the analysis.
/// Focuses on dictionary allocations, string formatting, and LINQ usage.
/// Reference: PERFORMANCE_ANALYSIS.md sections 1, 13
/// </summary>
[MemoryDiagnoser]
[SimpleJob(warmupCount: 3, iterationCount: 5)]
public class AllocationHotspotsBenchmarks {
string _subscriptionId = null!;
string _streamName = null!;
string _messageType = null!;

[GlobalSetup]
public void Setup() {
_subscriptionId = "test-subscription-id";
_streamName = "test-stream-name";
_messageType = "TestEventType";
}

[Benchmark(Baseline = true, Description = "Dictionary for logging scope (current)")]
public Dictionary<string, object> CreateLoggingScopeDictionary() {
return new() {
{ "SubscriptionId", _subscriptionId },
{ "Stream", _streamName },
{ "MessageType", _messageType }
};
}

[Benchmark(Description = "Array of KeyValuePairs (alternative)")]
public KeyValuePair<string, object>[] CreateLoggingScopeArray() {
return [
new("SubscriptionId", _subscriptionId),
new("Stream", _streamName),
new("MessageType", _messageType)
];
}

[Benchmark(Description = "Activity name - string interpolation")]
public string ActivityNameInterpolation() {
return $"Subscription.{_subscriptionId}/{_messageType}";
}

[Benchmark(Description = "Activity name - string concat")]
public string ActivityNameConcat() {
return string.Concat("Subscription.", _subscriptionId, "/", _messageType);
}

[Benchmark(Description = "Activity name - StringBuilder")]
public string ActivityNameStringBuilder() {
var sb = new StringBuilder(64);
sb.Append("Subscription.");
sb.Append(_subscriptionId);
sb.Append('/');
sb.Append(_messageType);
return sb.ToString();
}

[Benchmark(Description = "LINQ Any() check on small list")]
public bool LinqAnyOnSmallList() {
var list = new List<string> { "item1", "item2", "item3" };
return list.Any(x => x == "item2");
}

[Benchmark(Description = "Manual iteration on small list")]
public bool ManualIterationOnSmallList() {
var list = new List<string> { "item1", "item2", "item3" };
foreach (var item in list) {
if (item == "item2") return true;
}
return false;
}

[Benchmark(Description = "LINQ Where().Any() pattern")]
public bool LinqWhereAny() {
var items = Enumerable.Range(0, 20).Select(i => new TestItem { Id = i, Active = i % 2 == 0 });
return items.Any(x => x.Active);
}

[Benchmark(Description = "LINQ Any() with predicate")]
public bool LinqAnyWithPredicate() {
var items = Enumerable.Range(0, 20).Select(i => new TestItem { Id = i, Active = i % 2 == 0 });
return items.Any(x => x.Active);
}

[Benchmark(Description = "Manual enumeration check")]
public bool ManualEnumerationCheck() {
var items = Enumerable.Range(0, 20).Select(i => new TestItem { Id = i, Active = i % 2 == 0 });
foreach (var item in items) {
if (item.Active) return true;
}
return false;
}

[Benchmark(Description = "CancellationTokenSource creation")]
public CancellationTokenSource CreateCancellationTokenSource() {
var cts = new CancellationTokenSource();
cts.Dispose();
return cts;
}

[Benchmark(Description = "Linked CancellationTokenSource")]
public CancellationTokenSource CreateLinkedCancellationTokenSource() {
var cts1 = new CancellationTokenSource();
var cts2 = new CancellationTokenSource();
var linked = CancellationTokenSource.CreateLinkedTokenSource(cts1.Token, cts2.Token);
linked.Dispose();
cts2.Dispose();
cts1.Dispose();
return linked;
}

[Benchmark(Description = "Guid.ToString()")]
public string GuidToString() {
return Guid.NewGuid().ToString();
}

[Benchmark(Description = "DateTime.UtcNow allocation")]
public DateTime GetUtcNow() {
return DateTime.UtcNow;
}

class TestItem {
public int Id { get; set; }
public bool Active { get; set; }
}
}
4 changes: 4 additions & 0 deletions src/Benchmarks/Benchmarks/Benchmarks.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,16 @@
<ItemGroup>
<ProjectReference Include="$(CoreRoot)\Eventuous.Application\Eventuous.Application.csproj"/>
<ProjectReference Include="$(CoreRoot)\Eventuous.Subscriptions\Eventuous.Subscriptions.csproj"/>
<ProjectReference Include="$(SrcRoot)\Core\gen\Eventuous.Subscriptions.Generators\Eventuous.Subscriptions.Generators.csproj" OutputItemType="Analyzer" ReferenceOutputAssembly="false" />
<ProjectReference Include="$(SrcRoot)\Core\gen\Eventuous.Shared.Generators\Eventuous.Shared.Generators.csproj" OutputItemType="Analyzer" ReferenceOutputAssembly="false" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="BenchmarkDotNet"/>
</ItemGroup>
<ItemGroup>
<Compile Remove="GapDetectionBenchmarks.cs" />
<Compile Remove="TypeMapBenchmark.cs" />
<Compile Remove="FilterPipelineBenchmarks.cs" />
<Compile Remove="CheckpointBenchmarks.cs" />
</ItemGroup>
</Project>
83 changes: 83 additions & 0 deletions src/Benchmarks/Benchmarks/ChannelBatchingBenchmarks.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
using BenchmarkDotNet.Attributes;
using System.Buffers;

namespace Benchmarks;

/// <summary>
/// Benchmarks for channel batching ToArray optimization.
/// Tests different approaches to returning batched results.
/// Reference: PERFORMANCE_ANALYSIS.md - Issue #10: Channel Batching List.ToArray()
/// </summary>
[MemoryDiagnoser]
[SimpleJob(warmupCount: 3, iterationCount: 5)]
public class ChannelBatchingBenchmarks {
private List<int> _buffer = null!;

[Params(10, 50, 100)]
public int BatchSize { get; set; }

[GlobalSetup]
public void Setup() {
_buffer = new List<int>(BatchSize);
for (int i = 0; i < BatchSize; i++) {
_buffer.Add(i);
}
}

[Benchmark(Baseline = true, Description = "Current: List.ToArray()")]
public int[] CurrentApproach_ToArray() {
return _buffer.ToArray();
}

[Benchmark(Description = "Alternative 1: CollectionsMarshal.AsSpan()")]
public ReadOnlySpan<int> Alternative1_CollectionsMarshalAsSpan() {
return System.Runtime.InteropServices.CollectionsMarshal.AsSpan(_buffer);
}

[Benchmark(Description = "Alternative 2: ArrayPool rent/copy")]
public int[] Alternative2_ArrayPool() {
var array = ArrayPool<int>.Shared.Rent(_buffer.Count);
_buffer.CopyTo(array);
return array; // Note: caller must return to pool
}

[Benchmark(Description = "Alternative 3: Pre-allocated array with CopyTo")]
public int[] Alternative3_PreAllocatedArray() {
var array = new int[_buffer.Count];
_buffer.CopyTo(array);
return array;
}

[Benchmark(Description = "Alternative 4: Direct List (no copy)")]
public List<int> Alternative4_DirectList() {
return _buffer; // Returns list directly - consumer must handle as read-only
}

[Benchmark(Description = "Alternative 5: IReadOnlyList wrapper")]
public IReadOnlyList<int> Alternative5_ReadOnlyWrapper() {
return _buffer.AsReadOnly();
}

// Cleanup benchmark (shows ArrayPool return overhead)
private int[]? _rentedArray;

[IterationSetup(Target = nameof(WithArrayPoolReturnOverhead))]
public void SetupRentedArray() {
_rentedArray = ArrayPool<int>.Shared.Rent(_buffer.Count);
_buffer.CopyTo(_rentedArray);
}

[Benchmark(Description = "Alternative 2b: ArrayPool with return overhead")]
public int[] WithArrayPoolReturnOverhead() {
var result = _rentedArray;
return result!;
}

[IterationCleanup(Target = nameof(WithArrayPoolReturnOverhead))]
public void CleanupRentedArray() {
if (_rentedArray != null) {
ArrayPool<int>.Shared.Return(_rentedArray);
_rentedArray = null;
}
}
}
Loading
Loading