diff --git a/frameworks/CSharp/aspnetcore/appsettings.postgresql.json b/frameworks/CSharp/aspnetcore/appsettings.postgresql.json index 423af4d19b0..897abdcd96f 100644 --- a/frameworks/CSharp/aspnetcore/appsettings.postgresql.json +++ b/frameworks/CSharp/aspnetcore/appsettings.postgresql.json @@ -1,4 +1,4 @@ { - "ConnectionString": "Server=tfb-database;Database=hello_world;User Id=benchmarkdbuser;Password=benchmarkdbpass;SSL Mode=Disable;Maximum Pool Size=512;NoResetOnClose=true;Enlist=false;Max Auto Prepare=4", + "ConnectionString": "Server=tfb-database;Database=hello_world;User Id=benchmarkdbuser;Password=benchmarkdbpass;SSL Mode=Disable;Maximum Pool Size=18;Enlist=false;Max Auto Prepare=4;Multiplexing=true;Write Coalescing Buffer Threshold Bytes=1000;", "Database": "postgresql" } diff --git a/frameworks/CSharp/aspnetcore/aspnetcore-aot.dockerfile b/frameworks/CSharp/aspnetcore/aspnetcore-aot.dockerfile index a7396e3b919..3859225dd2a 100644 --- a/frameworks/CSharp/aspnetcore/aspnetcore-aot.dockerfile +++ b/frameworks/CSharp/aspnetcore/aspnetcore-aot.dockerfile @@ -1,11 +1,11 @@ -FROM mcr.microsoft.com/dotnet/sdk:9.0 AS build +FROM mcr.microsoft.com/dotnet/sdk:10.0 AS build RUN apt-get update RUN apt-get -yqq install clang zlib1g-dev WORKDIR /app COPY src/Platform . RUN dotnet publish -c Release -o out /p:DatabaseProvider=Npgsql /p:PublishAot=true /p:OptimizationPreference=Speed /p:GarbageCollectionAdaptationMode=0 -FROM mcr.microsoft.com/dotnet/aspnet:9.0 AS runtime +FROM mcr.microsoft.com/dotnet/aspnet:10.0 AS runtime ENV URLS=http://+:8080 WORKDIR /app diff --git a/frameworks/CSharp/aspnetcore/aspnetcore-minimal.dockerfile b/frameworks/CSharp/aspnetcore/aspnetcore-minimal.dockerfile index 24893c9717a..96bfb5e1ab4 100644 --- a/frameworks/CSharp/aspnetcore/aspnetcore-minimal.dockerfile +++ b/frameworks/CSharp/aspnetcore/aspnetcore-minimal.dockerfile @@ -1,9 +1,9 @@ -FROM mcr.microsoft.com/dotnet/sdk:9.0 AS build +FROM mcr.microsoft.com/dotnet/sdk:10.0 AS build WORKDIR /app COPY src/Minimal . RUN dotnet publish -c Release -o out -FROM mcr.microsoft.com/dotnet/aspnet:9.0 AS runtime +FROM mcr.microsoft.com/dotnet/aspnet:10.0 AS runtime ENV URLS http://+:8080 ENV DOTNET_GCDynamicAdaptationMode=0 ENV DOTNET_ReadyToRun=0 diff --git a/frameworks/CSharp/aspnetcore/aspnetcore-mvc.dockerfile b/frameworks/CSharp/aspnetcore/aspnetcore-mvc.dockerfile index 6922a53bf2a..4382f7b4b3b 100644 --- a/frameworks/CSharp/aspnetcore/aspnetcore-mvc.dockerfile +++ b/frameworks/CSharp/aspnetcore/aspnetcore-mvc.dockerfile @@ -1,9 +1,9 @@ -FROM mcr.microsoft.com/dotnet/sdk:9.0 AS build +FROM mcr.microsoft.com/dotnet/sdk:10.0 AS build WORKDIR /app COPY src/Mvc . RUN dotnet publish -c Release -o out -FROM mcr.microsoft.com/dotnet/aspnet:9.0 AS runtime +FROM mcr.microsoft.com/dotnet/aspnet:10.0 AS runtime ENV URLS http://+:8080 ENV DOTNET_GCDynamicAdaptationMode=0 ENV DOTNET_ReadyToRun=0 diff --git a/frameworks/CSharp/aspnetcore/aspnetcore-mysql.dockerfile b/frameworks/CSharp/aspnetcore/aspnetcore-mysql.dockerfile index ecc0a8331c3..19ff86e3dc9 100644 --- a/frameworks/CSharp/aspnetcore/aspnetcore-mysql.dockerfile +++ b/frameworks/CSharp/aspnetcore/aspnetcore-mysql.dockerfile @@ -1,9 +1,9 @@ -FROM mcr.microsoft.com/dotnet/sdk:9.0 AS build +FROM mcr.microsoft.com/dotnet/sdk:10.0 AS build WORKDIR /app COPY src/Platform . RUN dotnet publish -c Release -o out /p:DatabaseProvider=MySqlConnector -FROM mcr.microsoft.com/dotnet/aspnet:9.0 AS runtime +FROM mcr.microsoft.com/dotnet/aspnet:10.0 AS runtime ENV URLS http://+:8080 ENV DOTNET_GCDynamicAdaptationMode=0 ENV DOTNET_ReadyToRun=0 diff --git a/frameworks/CSharp/aspnetcore/aspnetcore.dockerfile b/frameworks/CSharp/aspnetcore/aspnetcore.dockerfile index 64510ffe786..4aa5b466752 100644 --- a/frameworks/CSharp/aspnetcore/aspnetcore.dockerfile +++ b/frameworks/CSharp/aspnetcore/aspnetcore.dockerfile @@ -1,9 +1,9 @@ -FROM mcr.microsoft.com/dotnet/sdk:9.0 AS build +FROM mcr.microsoft.com/dotnet/sdk:10.0 AS build WORKDIR /app COPY src/Platform . RUN dotnet publish -c Release -o out /p:DatabaseProvider=Npgsql -FROM mcr.microsoft.com/dotnet/aspnet:9.0 AS runtime +FROM mcr.microsoft.com/dotnet/aspnet:10.0 AS runtime ENV URLS=http://+:8080 ENV DOTNET_GCDynamicAdaptationMode=0 ENV DOTNET_ReadyToRun=0 diff --git a/frameworks/CSharp/aspnetcore/src/Minimal/Database/Db.cs b/frameworks/CSharp/aspnetcore/src/Minimal/Database/Db.cs index ce23c943abe..09ea9e26ce6 100644 --- a/frameworks/CSharp/aspnetcore/src/Minimal/Database/Db.cs +++ b/frameworks/CSharp/aspnetcore/src/Minimal/Database/Db.cs @@ -63,6 +63,9 @@ public async Task LoadMultipleUpdatesRows(string? parameter) var parameters = new Dictionary(); + var ids = new int[count]; + var numbers = new int[count]; + await using var db = _dbProviderFactory.CreateConnection(); db!.ConnectionString = _connectionString; @@ -72,18 +75,33 @@ public async Task LoadMultipleUpdatesRows(string? parameter) for (var i = 0; i < count; i++) { results[i] = await ReadSingleRow(db); + ids[i] = results[i].Id; } + Array.Sort(ids); + + // Ensure unique ids by incrementing duplicates + for (var i = 1; i < count; i++) + if (ids[i] == ids[i - 1]) + ids[i] = (ids[i] % 10000) + 1; for (var i = 0; i < count; i++) { var randomNumber = Random.Shared.Next(1, 10001); - parameters[$"@Rn_{i}"] = randomNumber; - parameters[$"@Id_{i}"] = results[i].Id; + if (results[i].RandomNumber == randomNumber) + { + randomNumber = (randomNumber % 10000) + 1; + } results[i].RandomNumber = randomNumber; + numbers[i] = randomNumber; } - await db.ExecuteAsync(BatchUpdateString.Query(count), parameters); + var update = "UPDATE world w SET randomnumber = u.new_val FROM (SELECT unnest(@ids) as id, unnest(@newValues) as new_val) u WHERE w.id = u.id"; + + parameters["ids"] = ids; + parameters["newValues"] = numbers; + + await db.ExecuteAsync(update, parameters); return results; } diff --git a/frameworks/CSharp/aspnetcore/src/Minimal/Minimal.csproj b/frameworks/CSharp/aspnetcore/src/Minimal/Minimal.csproj index 12f3446d8b7..256a58e1de0 100644 --- a/frameworks/CSharp/aspnetcore/src/Minimal/Minimal.csproj +++ b/frameworks/CSharp/aspnetcore/src/Minimal/Minimal.csproj @@ -1,7 +1,7 @@  - net9.0 + net10.0 enable enable latest @@ -9,9 +9,9 @@ - - - + + + diff --git a/frameworks/CSharp/aspnetcore/src/Mvc/Mvc.csproj b/frameworks/CSharp/aspnetcore/src/Mvc/Mvc.csproj index 68c70002683..4cdddc7c2d6 100644 --- a/frameworks/CSharp/aspnetcore/src/Mvc/Mvc.csproj +++ b/frameworks/CSharp/aspnetcore/src/Mvc/Mvc.csproj @@ -1,7 +1,7 @@  - net9.0 + net10.0 enable enable latest @@ -9,8 +9,8 @@ - - + + diff --git a/frameworks/CSharp/aspnetcore/src/Platform/Data/Random.cs b/frameworks/CSharp/aspnetcore/src/Platform/Data/Random.cs deleted file mode 100644 index a989eb9de2a..00000000000 --- a/frameworks/CSharp/aspnetcore/src/Platform/Data/Random.cs +++ /dev/null @@ -1,31 +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/frameworks/CSharp/aspnetcore/src/Platform/Data/RawDbMySqlConnector.cs b/frameworks/CSharp/aspnetcore/src/Platform/Data/RawDbMySqlConnector.cs index 340fa1236b2..5c7779b70f0 100644 --- a/frameworks/CSharp/aspnetcore/src/Platform/Data/RawDbMySqlConnector.cs +++ b/frameworks/CSharp/aspnetcore/src/Platform/Data/RawDbMySqlConnector.cs @@ -17,16 +17,14 @@ namespace PlatformBenchmarks; // If you are changing RawDbMySqlConnector.cs, also consider changing RawDbNpgsql.cs. public sealed class RawDb { - private readonly ConcurrentRandom _random; private readonly string _connectionString; private readonly MemoryCache _cache = new(new MemoryCacheOptions { ExpirationScanFrequency = TimeSpan.FromMinutes(60) }); private readonly MySqlDataSource _dataSource; - public RawDb(ConcurrentRandom random, AppSettings appSettings) + public RawDb(AppSettings appSettings) { - _random = random; _connectionString = appSettings.ConnectionString; _dataSource = new MySqlDataSource(appSettings.ConnectionString); } @@ -46,10 +44,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)) { @@ -80,7 +78,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.Value = id; key = cacheKeys[id]; } @@ -117,24 +115,18 @@ public async Task LoadMultipleQueriesRows(int count) using var connection = await _dataSource.OpenConnectionAsync(); - using var batch = new MySqlBatch(connection); - - for (var i = 0; i < count; i++) + // It is not acceptable to execute multiple SELECTs within a single complex query. + // It is not acceptable to retrieve all required rows using a SELECT ... WHERE id IN (...) clause. + // Pipelining of network traffic between the application and database is permitted. + + var (queryCmd, queryParameter) = await CreateReadCommandAsync(connection); + using (queryCmd) { - batch.BatchCommands.Add(new MySqlBatchCommand() + for (var i = 0; i < results.Length; i++) { - CommandText = "SELECT id, randomnumber FROM world WHERE id = @id", - Parameters = { new MySqlParameter("@id", _random.Next(1, 10001)) } - }); - } - - using var reader = await batch.ExecuteReaderAsync(); - - for (var i = 0; i < count; i++) - { - await reader.ReadAsync(); - results[i] = new World { Id = reader.GetInt32(0), RandomNumber = reader.GetInt32(1) }; - await reader.NextResultAsync(); + queryParameter.Value = Random.Shared.Next(1, 10001); + results[i] = await ReadSingleRow(queryCmd); + } } return results; @@ -144,23 +136,34 @@ 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 = await _dataSource.OpenConnectionAsync(); + // Each row must be selected randomly using one query in the same fashion as the single database query test + // Use of IN clauses or similar means to consolidate multiple queries into one operation is not permitted. + // Similarly, use of a batch or multiple SELECTs within a single statement are not permitted var (queryCmd, queryParameter) = await CreateReadCommandAsync(connection); using (queryCmd) { for (var i = 0; i < results.Length; i++) { + queryParameter.Value = ids[i]; results[i] = await ReadSingleRow(queryCmd); - queryParameter.Value = _random.Next(1, 10001); } } + // MySql doesn't have the unnest function like PostgreSQL, so we have to do a batch update instead using (var updateCmd = new MySqlCommand(BatchUpdateString.Query(count), connection)) { for (var i = 0; i < results.Length; i++) { - var randomNumber = _random.Next(1, 10001); + var randomNumber = Random.Shared.Next(1, 10001); updateCmd.Parameters.AddWithValue($"@Id_{i}", results[i].Id); updateCmd.Parameters.AddWithValue($"@Random_{i}", randomNumber); @@ -204,7 +207,7 @@ public async Task> LoadFortunesRows() private async Task<(MySqlCommand readCmd, MySqlParameter idParameter)> CreateReadCommandAsync(MySqlConnection connection) { var cmd = new MySqlCommand("SELECT id, randomnumber FROM world WHERE id = @Id", connection); - var parameter = new MySqlParameter("@Id", _random.Next(1, 10001)); + var parameter = new MySqlParameter("@Id", Random.Shared.Next(1, 10001)); cmd.Parameters.Add(parameter); diff --git a/frameworks/CSharp/aspnetcore/src/Platform/Data/RawDbNpgsql.cs b/frameworks/CSharp/aspnetcore/src/Platform/Data/RawDbNpgsql.cs index 60aeb9bd9a2..e8e05ab34e9 100644 --- a/frameworks/CSharp/aspnetcore/src/Platform/Data/RawDbNpgsql.cs +++ b/frameworks/CSharp/aspnetcore/src/Platform/Data/RawDbNpgsql.cs @@ -10,21 +10,20 @@ using System.Threading.Tasks; using Microsoft.Extensions.Caching.Memory; using Npgsql; +using NpgsqlTypes; namespace PlatformBenchmarks; public sealed class RawDb { - private readonly ConcurrentRandom _random; private readonly MemoryCache _cache = new(new MemoryCacheOptions { ExpirationScanFrequency = TimeSpan.FromMinutes(60) }); private readonly NpgsqlDataSource _dataSource; - public RawDb(ConcurrentRandom random, AppSettings appSettings) + public RawDb(AppSettings appSettings) { - _random = random; - _dataSource = NpgsqlDataSource.Create(appSettings.ConnectionString); + _dataSource = new NpgsqlSlimDataSourceBuilder(appSettings.ConnectionString).EnableArrays().Build(); } public async Task LoadSingleQueryRow() @@ -42,10 +41,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)) { @@ -76,7 +75,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]; } @@ -109,30 +108,18 @@ public async Task LoadMultipleQueriesRows(int count) using var connection = await _dataSource.OpenConnectionAsync(); - 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 - EnableErrorBarriers = true - }; - - for (var i = 0; i < count; i++) + // It is not acceptable to execute multiple SELECTs within a single complex query. + // It is not acceptable to retrieve all required rows using a SELECT ... WHERE id IN (...) clause. + // Pipelining of network traffic between the application and database is permitted. + + var (queryCmd, queryParameter) = CreateReadCommand(connection); + using (queryCmd) { - batch.BatchCommands.Add(new() + for (var i = 0; i < results.Length; i++) { - CommandText = "SELECT id, randomnumber FROM world WHERE id = $1", - Parameters = { new NpgsqlParameter { TypedValue = _random.Next(1, 10001) } } - }); - } - - using var reader = await batch.ExecuteReaderAsync(); - - for (var i = 0; i < count; i++) - { - await reader.ReadAsync(); - results[i] = new World { Id = reader.GetInt32(0), RandomNumber = reader.GetInt32(1) }; - await reader.NextResultAsync(); + queryParameter.TypedValue = Random.Shared.Next(1, 10001); + results[i] = await ReadSingleRow(queryCmd); + } } return results; @@ -142,34 +129,55 @@ public async Task LoadMultipleUpdatesRows(int count) { var results = new World[count]; - using var connection = CreateConnection(); - await connection.OpenAsync(); + var ids = new int[count]; + var numbers = new int[count]; + + for (var i = 0; i < count; i++) + { + ids[i] = Random.Shared.Next(1, 10001); + } + Array.Sort(ids); + + // Ensure unique ids by incrementing duplicates + for (var i = 1; i < count; i++) + if (ids[i] == ids[i - 1]) + ids[i] = (ids[i] % 10000) + 1; + + using var connection = await _dataSource.OpenConnectionAsync(); + // Each row must be selected randomly using one query in the same fashion as the single database query test + // Use of IN clauses or similar means to consolidate multiple queries into one operation is not permitted. + // Similarly, use of a batch or multiple SELECTs within a single statement are not permitted var (queryCmd, queryParameter) = CreateReadCommand(connection); using (queryCmd) { for (var i = 0; i < results.Length; i++) { + queryParameter.TypedValue = ids[i]; results[i] = await ReadSingleRow(queryCmd); - queryParameter.TypedValue = _random.Next(1, 10001); } } - using (var updateCmd = new NpgsqlCommand(BatchUpdateString.Query(count), connection)) + for (var i = 0; i < count; i++) { - for (var i = 0; i < results.Length; i++) + var randomNumber = Random.Shared.Next(1, 10001); + if (results[i].RandomNumber == randomNumber) { - var randomNumber = _random.Next(1, 10001); - - updateCmd.Parameters.Add(new NpgsqlParameter { TypedValue = results[i].Id }); - updateCmd.Parameters.Add(new NpgsqlParameter { TypedValue = randomNumber }); - - results[i].RandomNumber = randomNumber; + randomNumber = (randomNumber % 10000) + 1; } - await updateCmd.ExecuteNonQueryAsync(); + 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"; + + 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(); + return results; } @@ -203,7 +211,7 @@ public async Task> LoadFortunesRows() 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/frameworks/CSharp/aspnetcore/src/Platform/Platform.csproj b/frameworks/CSharp/aspnetcore/src/Platform/Platform.csproj index 7db51dc3ec7..46911b46548 100644 --- a/frameworks/CSharp/aspnetcore/src/Platform/Platform.csproj +++ b/frameworks/CSharp/aspnetcore/src/Platform/Platform.csproj @@ -1,7 +1,7 @@  - net9.0 + net10.0 true true preview @@ -18,10 +18,10 @@ - - - - + + + + diff --git a/frameworks/CSharp/aspnetcore/src/Platform/Program.cs b/frameworks/CSharp/aspnetcore/src/Platform/Program.cs index 28fc5d757dd..7877351f2da 100644 --- a/frameworks/CSharp/aspnetcore/src/Platform/Program.cs +++ b/frameworks/CSharp/aspnetcore/src/Platform/Program.cs @@ -7,6 +7,7 @@ using System.Threading.Tasks; using Microsoft.AspNetCore.Hosting; using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.Hosting; namespace PlatformBenchmarks; @@ -42,7 +43,7 @@ public static async Task Main(string[] args) await host.RunAsync(); } - public static IWebHost BuildWebHost(string[] args) + public static IHost BuildWebHost(string[] args) { Console.WriteLine($"BuildWebHost()"); Console.WriteLine($"Args: {string.Join(' ', args)}"); @@ -59,30 +60,34 @@ public static IWebHost BuildWebHost(string[] args) var appSettings = config.Get(); Console.WriteLine($"ConnectionString: {appSettings.ConnectionString}"); - BenchmarkApplication.RawDb = new RawDb(new ConcurrentRandom(), appSettings); + BenchmarkApplication.RawDb = new RawDb(appSettings); - var hostBuilder = new WebHostBuilder() - .UseBenchmarksConfiguration(config) - .UseKestrel((context, options) => + var hostBuilder = Host.CreateDefaultBuilder(args) + .ConfigureWebHost(webHostBuilder => { - var endPoint = context.Configuration.CreateIPEndPoint(); - - options.Listen(endPoint, builder => + webHostBuilder + .UseBenchmarksConfiguration(config) + .UseKestrel((context, options) => + { + var endPoint = context.Configuration.CreateIPEndPoint(); + + options.Listen(endPoint, builder => + { + builder.UseHttpApplication(); + }); + }) + .UseStartup(); + + webHostBuilder.UseSockets(options => { - builder.UseHttpApplication(); - }); - }) - .UseStartup(); - - hostBuilder.UseSockets(options => - { - options.WaitForDataBeforeAllocatingBuffer = false; + options.WaitForDataBeforeAllocatingBuffer = false; - if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux)) - { - options.UnsafePreferInlineScheduling = true; - } - }); + if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux)) + { + options.UnsafePreferInlineScheduling = true; + } + }); + }); var host = hostBuilder.Build();