From 25ca67296ff1b673ed5a8c7cb7f7068647409cad Mon Sep 17 00:00:00 2001 From: Sebastien Ros Date: Wed, 26 Nov 2025 08:59:39 -0800 Subject: [PATCH 1/5] Improve updates --- .../PlatformBenchmarks/Data/Random.cs | 32 ----------- .../PlatformBenchmarks/Data/RawDb.cs | 56 ++++++++----------- .../TechEmpower/PlatformBenchmarks/Program.cs | 2 +- 3 files changed, 25 insertions(+), 65 deletions(-) delete mode 100644 src/BenchmarksApps/TechEmpower/PlatformBenchmarks/Data/Random.cs diff --git a/src/BenchmarksApps/TechEmpower/PlatformBenchmarks/Data/Random.cs b/src/BenchmarksApps/TechEmpower/PlatformBenchmarks/Data/Random.cs deleted file mode 100644 index 19daabd05..000000000 --- a/src/BenchmarksApps/TechEmpower/PlatformBenchmarks/Data/Random.cs +++ /dev/null @@ -1,32 +0,0 @@ -// Copyright (c) .NET Foundation. All rights reserved. -// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. - -using System; -using System.Runtime.CompilerServices; -using System.Threading; - -namespace PlatformBenchmarks -{ - public sealed class ConcurrentRandom - { - private static int nextSeed = 0; - - // Random isn't thread safe - [ThreadStatic] - private static Random _random; - - private static Random Random => _random ?? CreateRandom(); - - [MethodImpl(MethodImplOptions.NoInlining)] - private static Random CreateRandom() - { - _random = new Random(Interlocked.Increment(ref nextSeed)); - return _random; - } - - public int Next(int minValue, int maxValue) - { - return Random.Next(minValue, maxValue); - } - } -} diff --git a/src/BenchmarksApps/TechEmpower/PlatformBenchmarks/Data/RawDb.cs b/src/BenchmarksApps/TechEmpower/PlatformBenchmarks/Data/RawDb.cs index 184cf2fb8..8761ffa5f 100644 --- a/src/BenchmarksApps/TechEmpower/PlatformBenchmarks/Data/RawDb.cs +++ b/src/BenchmarksApps/TechEmpower/PlatformBenchmarks/Data/RawDb.cs @@ -15,7 +15,6 @@ namespace PlatformBenchmarks { public sealed class RawDb { - private readonly ConcurrentRandom _random; private readonly MemoryCache _cache = new(new MemoryCacheOptions { ExpirationScanFrequency = TimeSpan.FromMinutes(60) }); @@ -25,9 +24,8 @@ private readonly MemoryCache _cache private readonly string _connectionString; #endif - public RawDb(ConcurrentRandom random, AppSettings appSettings) + public RawDb(AppSettings appSettings) { - _random = random; #if NET8_0_OR_GREATER _dataSource = new NpgsqlSlimDataSourceBuilder(appSettings.ConnectionString).Build(); #elif NET7_0 @@ -53,10 +51,10 @@ public Task LoadCachedQueries(int count) var result = new CachedWorld[count]; var cacheKeys = _cacheKeys; var cache = _cache; - var random = _random; + for (var i = 0; i < result.Length; i++) { - var id = random.Next(1, 10001); + var id = Random.Shared.Next(1, 10001); var key = cacheKeys[id]; if (cache.TryGetValue(key, out var cached)) { @@ -88,7 +86,7 @@ static async Task LoadUncachedQueries(int id, int i, int count, R { result[i] = await rawdb._cache.GetOrCreateAsync(key, create); - id = rawdb._random.Next(1, 10001); + id = Random.Shared.Next(1, 10001); idParameter.TypedValue = id; key = cacheKeys[id]; } @@ -136,7 +134,7 @@ public async Task LoadMultipleQueriesRows(int count) batch.BatchCommands.Add(new() { CommandText = "SELECT id, randomnumber FROM world WHERE id = $1", - Parameters = { new NpgsqlParameter { TypedValue = _random.Next(1, 10001) } } + Parameters = { new NpgsqlParameter { TypedValue = Random.Shared.Next(1, 10001) } } }); } @@ -165,7 +163,7 @@ public async Task LoadMultipleQueriesRows(int count) for (var i = 0; i < results.Length; i++) { results[i] = await ReadSingleRow(cmd); - idParameter.TypedValue = _random.Next(1, 10001); + idParameter.TypedValue = Random.Shared.Next(1, 10001); } return results; @@ -175,11 +173,11 @@ public async Task LoadMultipleQueriesRows(int count) public async Task LoadMultipleUpdatesRows(int count) { var results = new World[count]; + var ids = new int[count]; using var connection = CreateConnection(); await connection.OpenAsync(); -#if NET7_0_OR_GREATER using (var batch = new NpgsqlBatch(connection)) { // Inserts a PG Sync message between each statement in the batch, required for compliance with @@ -187,12 +185,18 @@ public async Task LoadMultipleUpdatesRows(int count) // https://github.com/TechEmpower/FrameworkBenchmarks/wiki/Project-Information-Framework-Tests-Overview batch.EnableErrorBarriers = true; + for (var i = 0; i < count; i++) + { + ids[i] = Random.Shared.Next(1, 10001); + } + Array.Sort(ids); + for (var i = 0; i < count; i++) { batch.BatchCommands.Add(new() { CommandText = "SELECT id, randomnumber FROM world WHERE id = $1", - Parameters = { new NpgsqlParameter { TypedValue = _random.Next(1, 10001) } } + Parameters = { new NpgsqlParameter { TypedValue = ids[i] } } }); } @@ -205,32 +209,20 @@ public async Task LoadMultipleUpdatesRows(int count) await reader.NextResultAsync(); } } -#else - var (queryCmd, queryParameter) = CreateReadCommand(connection); - using (queryCmd) - { - for (var i = 0; i < results.Length; i++) - { - results[i] = await ReadSingleRow(queryCmd); - queryParameter.TypedValue = _random.Next(1, 10001); - } - } -#endif - using (var updateCmd = new NpgsqlCommand(BatchUpdateString.Query(count), connection)) + var numbers = new int[count]; + for (var i = 0; i < count; i++) { - for (var i = 0; i < results.Length; i++) - { - var randomNumber = _random.Next(1, 10001); + numbers[i] = Random.Shared.Next(1, 10001); + } - updateCmd.Parameters.Add(new NpgsqlParameter { TypedValue = results[i].Id }); - updateCmd.Parameters.Add(new NpgsqlParameter { TypedValue = randomNumber }); + var update = "UPDATE world w SET randomnumber = u.new_val FROM (SELECT unnest($1) as id, unnest($2) as new_val) u WHERE w.id = u.id"; - results[i].RandomNumber = randomNumber; - } + using var updateCmd = new NpgsqlCommand(update, connection); + updateCmd.Parameters.AddWithValue("id", ids); + updateCmd.Parameters.AddWithValue("new_val", numbers); - await updateCmd.ExecuteNonQueryAsync(); - } + await updateCmd.ExecuteNonQueryAsync(); return results; } @@ -293,7 +285,7 @@ public Task> LoadFortunesRowsNoDb() private (NpgsqlCommand readCmd, NpgsqlParameter idParameter) CreateReadCommand(NpgsqlConnection connection) { var cmd = new NpgsqlCommand("SELECT id, randomnumber FROM world WHERE id = $1", connection); - var parameter = new NpgsqlParameter { TypedValue = _random.Next(1, 10001) }; + var parameter = new NpgsqlParameter { TypedValue = Random.Shared.Next(1, 10001) }; cmd.Parameters.Add(parameter); diff --git a/src/BenchmarksApps/TechEmpower/PlatformBenchmarks/Program.cs b/src/BenchmarksApps/TechEmpower/PlatformBenchmarks/Program.cs index 58238dbe2..ce466e4f0 100644 --- a/src/BenchmarksApps/TechEmpower/PlatformBenchmarks/Program.cs +++ b/src/BenchmarksApps/TechEmpower/PlatformBenchmarks/Program.cs @@ -73,7 +73,7 @@ public static IWebHost BuildWebHost(string[] args) if (appSettings.Database == DatabaseServer.PostgreSql) { - BenchmarkApplication.RawDb = new RawDb(new ConcurrentRandom(), appSettings); + BenchmarkApplication.RawDb = new RawDb(appSettings); BenchmarkApplication.DapperDb = new DapperDb(appSettings); BenchmarkApplication.EfDb = new EfDb(appSettings); } From 9f895bcf3f9b3b99be8d4c7106d6fb2f92e2c4a4 Mon Sep 17 00:00:00 2001 From: Sebastien Ros Date: Wed, 26 Nov 2025 09:18:39 -0800 Subject: [PATCH 2/5] Update parameters --- .../TechEmpower/PlatformBenchmarks/Data/RawDb.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/BenchmarksApps/TechEmpower/PlatformBenchmarks/Data/RawDb.cs b/src/BenchmarksApps/TechEmpower/PlatformBenchmarks/Data/RawDb.cs index 8761ffa5f..d1fbd7fb9 100644 --- a/src/BenchmarksApps/TechEmpower/PlatformBenchmarks/Data/RawDb.cs +++ b/src/BenchmarksApps/TechEmpower/PlatformBenchmarks/Data/RawDb.cs @@ -219,8 +219,8 @@ public async Task LoadMultipleUpdatesRows(int count) var update = "UPDATE world w SET randomnumber = u.new_val FROM (SELECT unnest($1) as id, unnest($2) as new_val) u WHERE w.id = u.id"; using var updateCmd = new NpgsqlCommand(update, connection); - updateCmd.Parameters.AddWithValue("id", ids); - updateCmd.Parameters.AddWithValue("new_val", numbers); + updateCmd.Parameters.Add(new NpgsqlParameter { TypedValue = ids }); + updateCmd.Parameters.Add(new NpgsqlParameter { TypedValue = numbers }); await updateCmd.ExecuteNonQueryAsync(); From 96b4b4dbd19ebaff0db76f86a27bd9f80cef11f6 Mon Sep 17 00:00:00 2001 From: Sebastien Ros Date: Wed, 26 Nov 2025 09:20:42 -0800 Subject: [PATCH 3/5] Update random number --- .../TechEmpower/PlatformBenchmarks/Data/RawDb.cs | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/BenchmarksApps/TechEmpower/PlatformBenchmarks/Data/RawDb.cs b/src/BenchmarksApps/TechEmpower/PlatformBenchmarks/Data/RawDb.cs index d1fbd7fb9..3bdfd03d6 100644 --- a/src/BenchmarksApps/TechEmpower/PlatformBenchmarks/Data/RawDb.cs +++ b/src/BenchmarksApps/TechEmpower/PlatformBenchmarks/Data/RawDb.cs @@ -173,7 +173,6 @@ public async Task LoadMultipleQueriesRows(int count) public async Task LoadMultipleUpdatesRows(int count) { var results = new World[count]; - var ids = new int[count]; using var connection = CreateConnection(); await connection.OpenAsync(); @@ -185,6 +184,7 @@ public async Task LoadMultipleUpdatesRows(int count) // https://github.com/TechEmpower/FrameworkBenchmarks/wiki/Project-Information-Framework-Tests-Overview batch.EnableErrorBarriers = true; + var ids = new int[count]; for (var i = 0; i < count; i++) { ids[i] = Random.Shared.Next(1, 10001); @@ -213,7 +213,9 @@ public async Task LoadMultipleUpdatesRows(int count) var numbers = new int[count]; for (var i = 0; i < count; i++) { - numbers[i] = Random.Shared.Next(1, 10001); + var randomNumber = Random.Shared.Next(1, 10001); + results[i].RandomNumber = randomNumber; + numbers[i] = randomNumber; } var update = "UPDATE world w SET randomnumber = u.new_val FROM (SELECT unnest($1) as id, unnest($2) as new_val) u WHERE w.id = u.id"; From c6f75fbbf304c6c5cd0ba5676e32c5beba69cc1b Mon Sep 17 00:00:00 2001 From: Sebastien Ros Date: Wed, 26 Nov 2025 09:23:59 -0800 Subject: [PATCH 4/5] Fix build --- .../TechEmpower/PlatformBenchmarks/Data/RawDb.cs | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/src/BenchmarksApps/TechEmpower/PlatformBenchmarks/Data/RawDb.cs b/src/BenchmarksApps/TechEmpower/PlatformBenchmarks/Data/RawDb.cs index 3bdfd03d6..462a5283f 100644 --- a/src/BenchmarksApps/TechEmpower/PlatformBenchmarks/Data/RawDb.cs +++ b/src/BenchmarksApps/TechEmpower/PlatformBenchmarks/Data/RawDb.cs @@ -174,6 +174,13 @@ public async Task LoadMultipleUpdatesRows(int count) { var results = new World[count]; + var ids = new int[count]; + for (var i = 0; i < count; i++) + { + ids[i] = Random.Shared.Next(1, 10001); + } + Array.Sort(ids); + using var connection = CreateConnection(); await connection.OpenAsync(); @@ -183,13 +190,6 @@ public async Task LoadMultipleUpdatesRows(int count) // TechEmpower general test requirement 7 // https://github.com/TechEmpower/FrameworkBenchmarks/wiki/Project-Information-Framework-Tests-Overview batch.EnableErrorBarriers = true; - - var ids = new int[count]; - for (var i = 0; i < count; i++) - { - ids[i] = Random.Shared.Next(1, 10001); - } - Array.Sort(ids); for (var i = 0; i < count; i++) { From 3338ed2245ae9433792b334ada2a8b725e157cb8 Mon Sep 17 00:00:00 2001 From: Sebastien Ros Date: Wed, 26 Nov 2025 10:59:55 -0800 Subject: [PATCH 5/5] Fix arrays support --- .../PlatformBenchmarks/Data/RawDb.cs | 7 +- src/BenchmarksApps/TechEmpower/apphost.cs | 98 +++++++++++++++++++ .../TechEmpower/apphost.run.json | 31 ++++++ 3 files changed, 133 insertions(+), 3 deletions(-) create mode 100644 src/BenchmarksApps/TechEmpower/apphost.cs create mode 100644 src/BenchmarksApps/TechEmpower/apphost.run.json diff --git a/src/BenchmarksApps/TechEmpower/PlatformBenchmarks/Data/RawDb.cs b/src/BenchmarksApps/TechEmpower/PlatformBenchmarks/Data/RawDb.cs index 462a5283f..08859a39b 100644 --- a/src/BenchmarksApps/TechEmpower/PlatformBenchmarks/Data/RawDb.cs +++ b/src/BenchmarksApps/TechEmpower/PlatformBenchmarks/Data/RawDb.cs @@ -8,6 +8,7 @@ using System.Threading.Tasks; using Microsoft.Extensions.Caching.Memory; using Npgsql; +using NpgsqlTypes; // ReSharper disable UseAwaitUsing @@ -27,7 +28,7 @@ private readonly MemoryCache _cache public RawDb(AppSettings appSettings) { #if NET8_0_OR_GREATER - _dataSource = new NpgsqlSlimDataSourceBuilder(appSettings.ConnectionString).Build(); + _dataSource = new NpgsqlSlimDataSourceBuilder(appSettings.ConnectionString).EnableArrays().Build(); #elif NET7_0 _dataSource = NpgsqlDataSource.Create(appSettings.ConnectionString); #else @@ -221,8 +222,8 @@ public async Task LoadMultipleUpdatesRows(int count) var update = "UPDATE world w SET randomnumber = u.new_val FROM (SELECT unnest($1) as id, unnest($2) as new_val) u WHERE w.id = u.id"; using var updateCmd = new NpgsqlCommand(update, connection); - updateCmd.Parameters.Add(new NpgsqlParameter { TypedValue = ids }); - updateCmd.Parameters.Add(new NpgsqlParameter { TypedValue = numbers }); + updateCmd.Parameters.Add(new NpgsqlParameter { TypedValue = ids, NpgsqlDbType = NpgsqlDbType.Array | NpgsqlDbType.Integer }); + updateCmd.Parameters.Add(new NpgsqlParameter { TypedValue = numbers, NpgsqlDbType = NpgsqlDbType.Array | NpgsqlDbType.Integer }); await updateCmd.ExecuteNonQueryAsync(); diff --git a/src/BenchmarksApps/TechEmpower/apphost.cs b/src/BenchmarksApps/TechEmpower/apphost.cs new file mode 100644 index 000000000..075873f17 --- /dev/null +++ b/src/BenchmarksApps/TechEmpower/apphost.cs @@ -0,0 +1,98 @@ +#:sdk Aspire.AppHost.Sdk@13.0.0 + +var builder = DistributedApplication.CreateBuilder(args); + +// Add the postgres database from Dockerfile +var postgres = builder.AddDockerfile("postgres-techempower", "../../../docker/postgres-techempower") + .WithEndpoint(port: 5432, targetPort: 5432, name: "tcp") + .WithEnvironment("POSTGRES_USER", "benchmarkdbuser") + .WithEnvironment("POSTGRES_PASSWORD", "benchmarkdbpass") + .WithEnvironment("POSTGRES_DB", "hello_world"); + +var postgresEndpoint = postgres.GetEndpoint("tcp"); +var connectionString = ReferenceExpression.Create($"Server={postgresEndpoint.Property(EndpointProperty.Host)};Port={postgresEndpoint.Property(EndpointProperty.Port)};Database=hello_world;User Id=benchmarkdbuser;Password=benchmarkdbpass"); + +// Add all TechEmpower benchmark applications +// Note: BlazorSSR, Minimal, Mvc only support net8.0. MvcFull targets .NET Framework 4.8. +// PlatformBenchmarks and RazorPages support net8.0;net9.0 multi-targeting. + +builder.AddProject("blazorssr", "BlazorSSR/BlazorSSR.csproj") + .WaitFor(postgres) + .WithEnvironment("ConnectionString", connectionString) + .WithUrls(context => + { + var http = context.GetEndpoint("http"); + if (http is not null) + { + context.Urls.Add(new() { Url = $"{http.Url}/plaintext", DisplayText = "plaintext" }); + context.Urls.Add(new() { Url = $"{http.Url}/json", DisplayText = "json" }); + context.Urls.Add(new() { Url = $"{http.Url}/fortunes", DisplayText = "fortunes" }); + context.Urls.Add(new() { Url = $"{http.Url}/updates/20", DisplayText = "updates" }); + } + }); + +builder.AddProject("minimal", "Minimal/Minimal.csproj") + .WaitFor(postgres) + .WithEnvironment("ConnectionString", connectionString) + .WithUrls(context => + { + var http = context.GetEndpoint("http"); + if (http is not null) + { + context.Urls.Add(new() { Url = $"{http.Url}/plaintext", DisplayText = "plaintext" }); + context.Urls.Add(new() { Url = $"{http.Url}/json", DisplayText = "json" }); + context.Urls.Add(new() { Url = $"{http.Url}/fortunes", DisplayText = "fortunes" }); + context.Urls.Add(new() { Url = $"{http.Url}/updates/20", DisplayText = "updates" }); + } + }); + +builder.AddProject("mvc", "Mvc/Mvc.csproj") + .WaitFor(postgres) + .WithEnvironment("ConnectionString", connectionString) + .WithUrls(context => + { + var http = context.GetEndpoint("http"); + if (http is not null) + { + context.Urls.Add(new() { Url = $"{http.Url}/plaintext", DisplayText = "plaintext" }); + context.Urls.Add(new() { Url = $"{http.Url}/json", DisplayText = "json" }); + context.Urls.Add(new() { Url = $"{http.Url}/fortunes", DisplayText = "fortunes" }); + context.Urls.Add(new() { Url = $"{http.Url}/updates/20", DisplayText = "updates" }); + } + }); + +builder.AddProject("platformbenchmarks", "PlatformBenchmarks/PlatformBenchmarks.csproj") + .WaitFor(postgres) + .WithEnvironment("ConnectionString", connectionString) + .WithArgs("--framework", "net9.0") + .WithArgs("-p:IsDatabase=true") + .WithEnvironment("DATABASE", "PostgreSql") + .WithUrls(context => + { + var http = context.GetEndpoint("http"); + if (http is not null) + { + context.Urls.Add(new() { Url = $"{http.Url}/plaintext", DisplayText = "plaintext" }); + context.Urls.Add(new() { Url = $"{http.Url}/json", DisplayText = "json" }); + context.Urls.Add(new() { Url = $"{http.Url}/fortunes", DisplayText = "fortunes" }); + context.Urls.Add(new() { Url = $"{http.Url}/updates/20", DisplayText = "updates" }); + } + }); + +builder.AddProject("razorpages", "RazorPages/RazorPages.csproj") + .WaitFor(postgres) + .WithEnvironment("ConnectionString", connectionString) + .WithArgs("--framework", "net9.0") + .WithUrls(context => + { + var http = context.GetEndpoint("http"); + if (http is not null) + { + context.Urls.Add(new() { Url = $"{http.Url}/plaintext", DisplayText = "plaintext" }); + context.Urls.Add(new() { Url = $"{http.Url}/json", DisplayText = "json" }); + context.Urls.Add(new() { Url = $"{http.Url}/fortunes", DisplayText = "fortunes" }); + context.Urls.Add(new() { Url = $"{http.Url}/updates/20", DisplayText = "updates" }); + } + }); + +builder.Build().Run(); diff --git a/src/BenchmarksApps/TechEmpower/apphost.run.json b/src/BenchmarksApps/TechEmpower/apphost.run.json new file mode 100644 index 000000000..ebba6957d --- /dev/null +++ b/src/BenchmarksApps/TechEmpower/apphost.run.json @@ -0,0 +1,31 @@ +{ + "$schema": "https://json.schemastore.org/launchsettings.json", + "profiles": { + "https": { + "commandName": "Project", + "dotnetRunMessages": true, + "launchBrowser": true, + "applicationUrl": "https://localhost:17005;http://localhost:15223", + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development", + "DOTNET_ENVIRONMENT": "Development", + "ASPIRE_DASHBOARD_OTLP_ENDPOINT_URL": "https://localhost:21012", + "ASPIRE_DASHBOARD_MCP_ENDPOINT_URL": "https://localhost:23127", + "ASPIRE_RESOURCE_SERVICE_ENDPOINT_URL": "https://localhost:22253" + } + }, + "http": { + "commandName": "Project", + "dotnetRunMessages": true, + "launchBrowser": true, + "applicationUrl": "http://localhost:15223", + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development", + "DOTNET_ENVIRONMENT": "Development", + "ASPIRE_DASHBOARD_OTLP_ENDPOINT_URL": "http://localhost:19080", + "ASPIRE_DASHBOARD_MCP_ENDPOINT_URL": "http://localhost:18252", + "ASPIRE_RESOURCE_SERVICE_ENDPOINT_URL": "http://localhost:20285" + } + } + } +}