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..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 @@ -15,7 +16,6 @@ namespace PlatformBenchmarks { public sealed class RawDb { - private readonly ConcurrentRandom _random; private readonly MemoryCache _cache = new(new MemoryCacheOptions { ExpirationScanFrequency = TimeSpan.FromMinutes(60) }); @@ -25,11 +25,10 @@ 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(); + _dataSource = new NpgsqlSlimDataSourceBuilder(appSettings.ConnectionString).EnableArrays().Build(); #elif NET7_0 _dataSource = NpgsqlDataSource.Create(appSettings.ConnectionString); #else @@ -53,10 +52,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 +87,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 +135,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 +164,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; @@ -176,23 +175,29 @@ 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(); -#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 // TechEmpower general test requirement 7 // https://github.com/TechEmpower/FrameworkBenchmarks/wiki/Project-Information-Framework-Tests-Overview batch.EnableErrorBarriers = true; - + 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 +210,22 @@ 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); + var randomNumber = Random.Shared.Next(1, 10001); + results[i].RandomNumber = randomNumber; + numbers[i] = randomNumber; + } - 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.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(); - } + await updateCmd.ExecuteNonQueryAsync(); return results; } @@ -293,7 +288,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); } 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" + } + } + } +}