From aba8713bc15ddf033ae7dc9f7e8f7f44d5c0b84e Mon Sep 17 00:00:00 2001 From: Damian Edwards Date: Tue, 28 Feb 2023 17:06:32 -0800 Subject: [PATCH 1/9] Update Fortunes to use Razor templating Updates the Fortunes implementations for Minimal and Platform to use Razor .cshtml templates with custom base classes intended for rendering using the RazorSlices package (no MVC). --- .gitignore | 3 + src/BenchmarksApps.sln | 141 +++++++++++ .../TechEmpower/Minimal/Database/Db.cs | 175 +++++++------- .../TechEmpower/Minimal/Minimal.csproj | 4 +- .../TechEmpower/Minimal/Program.cs | 20 +- .../Minimal/Templates/Fortunes.cshtml | 2 + .../Minimal/Templates/FortunesTemplate.cs | 26 --- .../Minimal/Templates/_ViewImports.cshtml | 10 + .../TechEmpower/Minimal/appsettings.json | 3 +- .../Minimal/minimal.benchmarks.yml | 2 +- .../BenchmarkApplication.Fortunes.cs | 135 ++++------- .../BenchmarkApplication.HttpConnection.cs | 11 + .../BenchmarkApplication.cs | 28 ++- .../PlatformBenchmarks/ChunkedBufferWriter.cs | 219 ++++++++++++++++++ .../PlatformBenchmarks/Data/Fortune.cs | 6 +- .../PlatformBenchmarks/Data/RawDb.cs | 3 +- .../PlatformBenchmarks.csproj | 21 +- .../TechEmpower/PlatformBenchmarks/Program.cs | 12 +- .../Templates/Fortunes.cshtml | 2 + .../Templates/FortunesDapper.cshtml | 2 + .../Templates/FortunesEf.cshtml | 2 + .../Templates/_ViewImports.cshtml | 9 + .../PlatformBenchmarks/appsettings.json | 3 +- 23 files changed, 604 insertions(+), 235 deletions(-) create mode 100644 src/BenchmarksApps.sln create mode 100644 src/BenchmarksApps/TechEmpower/Minimal/Templates/Fortunes.cshtml delete mode 100644 src/BenchmarksApps/TechEmpower/Minimal/Templates/FortunesTemplate.cs create mode 100644 src/BenchmarksApps/TechEmpower/Minimal/Templates/_ViewImports.cshtml create mode 100644 src/BenchmarksApps/TechEmpower/PlatformBenchmarks/ChunkedBufferWriter.cs create mode 100644 src/BenchmarksApps/TechEmpower/PlatformBenchmarks/Templates/Fortunes.cshtml create mode 100644 src/BenchmarksApps/TechEmpower/PlatformBenchmarks/Templates/FortunesDapper.cshtml create mode 100644 src/BenchmarksApps/TechEmpower/PlatformBenchmarks/Templates/FortunesEf.cshtml create mode 100644 src/BenchmarksApps/TechEmpower/PlatformBenchmarks/Templates/_ViewImports.cshtml diff --git a/.gitignore b/.gitignore index 818095733..a3e8fa6c9 100644 --- a/.gitignore +++ b/.gitignore @@ -17,6 +17,9 @@ global.json # User-specific files (MonoDevelop/Xamarin Studio) *.userprefs +# Crank results +.crank/ + # Build results [Dd]ebug/ [Dd]ebugPublic/ diff --git a/src/BenchmarksApps.sln b/src/BenchmarksApps.sln new file mode 100644 index 000000000..0b10ce2a9 --- /dev/null +++ b/src/BenchmarksApps.sln @@ -0,0 +1,141 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Version 17 +VisualStudioVersion = 17.6.33416.333 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "BasicMinimalApi", "BenchmarksApps\BasicMinimalApi\BasicMinimalApi.csproj", "{D13E5EF8-8DA6-4904-891D-1A8D8AAC04DD}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "HelloWorldMiddleware", "BenchmarksApps\HelloWorldMiddleware\HelloWorldMiddleware.csproj", "{48EDA7B2-BFBF-4634-BA8B-81608F78D93C}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "HelloWorldMvc", "BenchmarksApps\HelloWorldMvc\HelloWorldMvc.csproj", "{23CDC3E1-C073-4D8C-92D6-425E181CD5E3}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MapAction", "BenchmarksApps\MapAction\MapAction.csproj", "{58029BDE-4A26-4819-8776-A1B1B6075C51}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Mvc", "BenchmarksApps\Mvc\Mvc.csproj", "{34448A4F-80AC-4795-B644-BED3227A0086}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "StaticFiles", "BenchmarksApps\StaticFiles\StaticFiles.csproj", "{EB296FAF-53D2-4E93-B6E5-C679FD3AE73D}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "DistributedCache", "BenchmarksApps\DistributedCache\DistributedCache.csproj", "{AB0D447E-476F-4482-A844-BC2654C308F5}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Grpc", "Grpc", "{D8A014FB-3C99-4831-9FFB-F4A89A48D8BD}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "BasicGrpc", "BenchmarksApps\Grpc\BasicGrpc\BasicGrpc.csproj", "{8DF3A6BB-E8C5-4FAA-839A-D185C9F93CD5}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "GrpcHttpApiServer", "GrpcHttpApiServer", "{9E4AF835-2A78-4012-B0B5-9DA41ACDC716}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Server", "BenchmarksApps\Grpc\GrpcHttpApiServer\Server\Server.csproj", "{20757830-EA66-4962-BDBB-A101A2062A2C}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "TechEmpower", "TechEmpower", "{B6DB234C-8F80-4160-B95D-D70AFC444A3D}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Minimal", "BenchmarksApps\TechEmpower\Minimal\Minimal.csproj", "{07C0B18B-9738-4349-A8DF-3E88D3DF90AE}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Mvc", "BenchmarksApps\TechEmpower\Mvc\Mvc.csproj", "{E68B58F8-40EA-49EA-A126-0B67F2BE7343}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "PlatformBenchmarks", "BenchmarksApps\TechEmpower\PlatformBenchmarks\PlatformBenchmarks.csproj", "{ACA43671-AD28-4F72-AAAB-6C32B388C2F0}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{689C58F6-8DF0-4565-887F-C9A9BDA757D8}" + ProjectSection(SolutionItems) = preProject + Directory.Build.props = Directory.Build.props + Directory.Build.targets = Directory.Build.targets + EndProjectSection +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "BuildPerformance", "BenchmarksApps\BuildPerformance\BuildPerformance.csproj", "{2E953AFB-4900-4B5D-9E78-819E950CD365}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "SignalR", "SignalR", "{398A40DA-FE1D-4B4D-A580-A33E29885553}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "BenchmarkServer", "BenchmarksApps\SignalR\BenchmarkServer.csproj", "{D8F11F87-823F-4864-926D-5F66448A5C13}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Websocket", "Websocket", "{6A69DE6C-07A6-4ABE-A4D2-0F983A33BBF8}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "BenchmarkServer", "BenchmarksApps\Websocket\BenchmarkServer.csproj", "{3D2573DE-CE7A-4CB8-A980-8C8636EE059E}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "TcpEcho", "BenchmarksApps\TcpEcho\TcpEcho.csproj", "{436118C1-B9A7-4966-86AB-6F2477B6B201}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {D13E5EF8-8DA6-4904-891D-1A8D8AAC04DD}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {D13E5EF8-8DA6-4904-891D-1A8D8AAC04DD}.Debug|Any CPU.Build.0 = Debug|Any CPU + {D13E5EF8-8DA6-4904-891D-1A8D8AAC04DD}.Release|Any CPU.ActiveCfg = Release|Any CPU + {D13E5EF8-8DA6-4904-891D-1A8D8AAC04DD}.Release|Any CPU.Build.0 = Release|Any CPU + {48EDA7B2-BFBF-4634-BA8B-81608F78D93C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {48EDA7B2-BFBF-4634-BA8B-81608F78D93C}.Debug|Any CPU.Build.0 = Debug|Any CPU + {48EDA7B2-BFBF-4634-BA8B-81608F78D93C}.Release|Any CPU.ActiveCfg = Release|Any CPU + {48EDA7B2-BFBF-4634-BA8B-81608F78D93C}.Release|Any CPU.Build.0 = Release|Any CPU + {23CDC3E1-C073-4D8C-92D6-425E181CD5E3}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {23CDC3E1-C073-4D8C-92D6-425E181CD5E3}.Debug|Any CPU.Build.0 = Debug|Any CPU + {23CDC3E1-C073-4D8C-92D6-425E181CD5E3}.Release|Any CPU.ActiveCfg = Release|Any CPU + {23CDC3E1-C073-4D8C-92D6-425E181CD5E3}.Release|Any CPU.Build.0 = Release|Any CPU + {58029BDE-4A26-4819-8776-A1B1B6075C51}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {58029BDE-4A26-4819-8776-A1B1B6075C51}.Debug|Any CPU.Build.0 = Debug|Any CPU + {58029BDE-4A26-4819-8776-A1B1B6075C51}.Release|Any CPU.ActiveCfg = Release|Any CPU + {58029BDE-4A26-4819-8776-A1B1B6075C51}.Release|Any CPU.Build.0 = Release|Any CPU + {34448A4F-80AC-4795-B644-BED3227A0086}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {34448A4F-80AC-4795-B644-BED3227A0086}.Debug|Any CPU.Build.0 = Debug|Any CPU + {34448A4F-80AC-4795-B644-BED3227A0086}.Release|Any CPU.ActiveCfg = Release|Any CPU + {34448A4F-80AC-4795-B644-BED3227A0086}.Release|Any CPU.Build.0 = Release|Any CPU + {EB296FAF-53D2-4E93-B6E5-C679FD3AE73D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {EB296FAF-53D2-4E93-B6E5-C679FD3AE73D}.Debug|Any CPU.Build.0 = Debug|Any CPU + {EB296FAF-53D2-4E93-B6E5-C679FD3AE73D}.Release|Any CPU.ActiveCfg = Release|Any CPU + {EB296FAF-53D2-4E93-B6E5-C679FD3AE73D}.Release|Any CPU.Build.0 = Release|Any CPU + {AB0D447E-476F-4482-A844-BC2654C308F5}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {AB0D447E-476F-4482-A844-BC2654C308F5}.Debug|Any CPU.Build.0 = Debug|Any CPU + {AB0D447E-476F-4482-A844-BC2654C308F5}.Release|Any CPU.ActiveCfg = Release|Any CPU + {AB0D447E-476F-4482-A844-BC2654C308F5}.Release|Any CPU.Build.0 = Release|Any CPU + {8DF3A6BB-E8C5-4FAA-839A-D185C9F93CD5}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {8DF3A6BB-E8C5-4FAA-839A-D185C9F93CD5}.Debug|Any CPU.Build.0 = Debug|Any CPU + {8DF3A6BB-E8C5-4FAA-839A-D185C9F93CD5}.Release|Any CPU.ActiveCfg = Release|Any CPU + {8DF3A6BB-E8C5-4FAA-839A-D185C9F93CD5}.Release|Any CPU.Build.0 = Release|Any CPU + {20757830-EA66-4962-BDBB-A101A2062A2C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {20757830-EA66-4962-BDBB-A101A2062A2C}.Debug|Any CPU.Build.0 = Debug|Any CPU + {20757830-EA66-4962-BDBB-A101A2062A2C}.Release|Any CPU.ActiveCfg = Release|Any CPU + {20757830-EA66-4962-BDBB-A101A2062A2C}.Release|Any CPU.Build.0 = Release|Any CPU + {07C0B18B-9738-4349-A8DF-3E88D3DF90AE}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {07C0B18B-9738-4349-A8DF-3E88D3DF90AE}.Debug|Any CPU.Build.0 = Debug|Any CPU + {07C0B18B-9738-4349-A8DF-3E88D3DF90AE}.Release|Any CPU.ActiveCfg = Release|Any CPU + {07C0B18B-9738-4349-A8DF-3E88D3DF90AE}.Release|Any CPU.Build.0 = Release|Any CPU + {E68B58F8-40EA-49EA-A126-0B67F2BE7343}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {E68B58F8-40EA-49EA-A126-0B67F2BE7343}.Debug|Any CPU.Build.0 = Debug|Any CPU + {E68B58F8-40EA-49EA-A126-0B67F2BE7343}.Release|Any CPU.ActiveCfg = Release|Any CPU + {E68B58F8-40EA-49EA-A126-0B67F2BE7343}.Release|Any CPU.Build.0 = Release|Any CPU + {ACA43671-AD28-4F72-AAAB-6C32B388C2F0}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {ACA43671-AD28-4F72-AAAB-6C32B388C2F0}.Debug|Any CPU.Build.0 = Debug|Any CPU + {ACA43671-AD28-4F72-AAAB-6C32B388C2F0}.Release|Any CPU.ActiveCfg = Release|Any CPU + {ACA43671-AD28-4F72-AAAB-6C32B388C2F0}.Release|Any CPU.Build.0 = Release|Any CPU + {2E953AFB-4900-4B5D-9E78-819E950CD365}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {2E953AFB-4900-4B5D-9E78-819E950CD365}.Debug|Any CPU.Build.0 = Debug|Any CPU + {2E953AFB-4900-4B5D-9E78-819E950CD365}.Release|Any CPU.ActiveCfg = Release|Any CPU + {2E953AFB-4900-4B5D-9E78-819E950CD365}.Release|Any CPU.Build.0 = Release|Any CPU + {D8F11F87-823F-4864-926D-5F66448A5C13}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {D8F11F87-823F-4864-926D-5F66448A5C13}.Debug|Any CPU.Build.0 = Debug|Any CPU + {D8F11F87-823F-4864-926D-5F66448A5C13}.Release|Any CPU.ActiveCfg = Release|Any CPU + {D8F11F87-823F-4864-926D-5F66448A5C13}.Release|Any CPU.Build.0 = Release|Any CPU + {3D2573DE-CE7A-4CB8-A980-8C8636EE059E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {3D2573DE-CE7A-4CB8-A980-8C8636EE059E}.Debug|Any CPU.Build.0 = Debug|Any CPU + {3D2573DE-CE7A-4CB8-A980-8C8636EE059E}.Release|Any CPU.ActiveCfg = Release|Any CPU + {3D2573DE-CE7A-4CB8-A980-8C8636EE059E}.Release|Any CPU.Build.0 = Release|Any CPU + {436118C1-B9A7-4966-86AB-6F2477B6B201}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {436118C1-B9A7-4966-86AB-6F2477B6B201}.Debug|Any CPU.Build.0 = Debug|Any CPU + {436118C1-B9A7-4966-86AB-6F2477B6B201}.Release|Any CPU.ActiveCfg = Release|Any CPU + {436118C1-B9A7-4966-86AB-6F2477B6B201}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(NestedProjects) = preSolution + {8DF3A6BB-E8C5-4FAA-839A-D185C9F93CD5} = {D8A014FB-3C99-4831-9FFB-F4A89A48D8BD} + {9E4AF835-2A78-4012-B0B5-9DA41ACDC716} = {D8A014FB-3C99-4831-9FFB-F4A89A48D8BD} + {20757830-EA66-4962-BDBB-A101A2062A2C} = {9E4AF835-2A78-4012-B0B5-9DA41ACDC716} + {07C0B18B-9738-4349-A8DF-3E88D3DF90AE} = {B6DB234C-8F80-4160-B95D-D70AFC444A3D} + {E68B58F8-40EA-49EA-A126-0B67F2BE7343} = {B6DB234C-8F80-4160-B95D-D70AFC444A3D} + {ACA43671-AD28-4F72-AAAB-6C32B388C2F0} = {B6DB234C-8F80-4160-B95D-D70AFC444A3D} + {D8F11F87-823F-4864-926D-5F66448A5C13} = {398A40DA-FE1D-4B4D-A580-A33E29885553} + {3D2573DE-CE7A-4CB8-A980-8C8636EE059E} = {6A69DE6C-07A6-4ABE-A4D2-0F983A33BBF8} + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {117072DC-DE12-4F74-90CA-692FA2BE8DCB} + EndGlobalSection +EndGlobal diff --git a/src/BenchmarksApps/TechEmpower/Minimal/Database/Db.cs b/src/BenchmarksApps/TechEmpower/Minimal/Database/Db.cs index a701dfc2a..fd1c16501 100644 --- a/src/BenchmarksApps/TechEmpower/Minimal/Database/Db.cs +++ b/src/BenchmarksApps/TechEmpower/Minimal/Database/Db.cs @@ -2,118 +2,117 @@ using Dapper; using Minimal.Models; -namespace Minimal.Database +namespace Minimal.Database; + +public class Db { - public class Db + private static readonly Comparison FortuneSortComparison = (a, b) => string.CompareOrdinal(a.Message, b.Message); + + private static readonly Random _random = Random.Shared; + + private readonly DbProviderFactory _dbProviderFactory; + private readonly string _connectionString; + + public Db(AppSettings appSettings) { - private static readonly Comparison FortuneSortComparison = (a, b) => string.CompareOrdinal(a.Message, b.Message); + ArgumentException.ThrowIfNullOrEmpty(appSettings.ConnectionString); - private static readonly Random _random = Random.Shared; + _dbProviderFactory = Npgsql.NpgsqlFactory.Instance; + _connectionString = appSettings.ConnectionString; + } - private readonly DbProviderFactory _dbProviderFactory; - private readonly string _connectionString; + public Task LoadSingleQueryRow() + { + using var db = _dbProviderFactory.CreateConnection(); + db!.ConnectionString = _connectionString; - public Db(AppSettings appSettings) - { - ArgumentNullException.ThrowIfNull(appSettings.ConnectionString); + // Note: Don't need to open connection if only doing one thing; let dapper do it + return ReadSingleRow(db); + } - _dbProviderFactory = Npgsql.NpgsqlFactory.Instance; - _connectionString = appSettings.ConnectionString; - } + static Task ReadSingleRow(DbConnection db) + { + return db.QueryFirstOrDefaultAsync( + "SELECT id, randomnumber FROM world WHERE id = @Id", + new { Id = _random.Next(1, 10001) }); + } - public Task LoadSingleQueryRow() + public async Task LoadMultipleQueriesRows(int count) + { + if (count <= 0) { - using var db = _dbProviderFactory.CreateConnection(); - db!.ConnectionString = _connectionString; - - // Note: Don't need to open connection if only doing one thing; let dapper do it - return ReadSingleRow(db); + count = 1; + } + else if (count > 500) + { + count = 500; } - static Task ReadSingleRow(DbConnection db) + var results = new World[count]; + using var db = _dbProviderFactory.CreateConnection(); + + db!.ConnectionString = _connectionString; + await db.OpenAsync(); + + for (var i = 0; i < count; i++) { - return db.QueryFirstOrDefaultAsync( - "SELECT id, randomnumber FROM world WHERE id = @Id", - new { Id = _random.Next(1, 10001) }); + results[i] = await ReadSingleRow(db); } - public async Task LoadMultipleQueriesRows(int count) + return results; + } + + public async Task LoadMultipleUpdatesRows(int count) + { + if (count <= 0) { - if (count <= 0) - { - count = 1; - } - else if (count > 500) - { - count = 500; - } - - var results = new World[count]; - using var db = _dbProviderFactory.CreateConnection(); - - db!.ConnectionString = _connectionString; - await db.OpenAsync(); - - for (var i = 0; i < count; i++) - { - results[i] = await ReadSingleRow(db); - } - - return results; + count = 1; } + else if (count > 500) + { + count = 500; + } + + var parameters = new Dictionary(); + + using var db = _dbProviderFactory.CreateConnection(); - public async Task LoadMultipleUpdatesRows(int count) + db!.ConnectionString = _connectionString; + await db.OpenAsync(); + + var results = new World[count]; + for (var i = 0; i < count; i++) { - if (count <= 0) - { - count = 1; - } - else if (count > 500) - { - count = 500; - } - - var parameters = new Dictionary(); - - using var db = _dbProviderFactory.CreateConnection(); - - db!.ConnectionString = _connectionString; - await db.OpenAsync(); - - var results = new World[count]; - for (var i = 0; i < count; i++) - { - results[i] = await ReadSingleRow(db); - } - - for (var i = 0; i < count; i++) - { - var randomNumber = _random.Next(1, 10001); - parameters[$"@Rn_{i}"] = randomNumber; - parameters[$"@Id_{i}"] = results[i].Id; - - results[i].RandomNumber = randomNumber; - } - - await db.ExecuteAsync(BatchUpdateString.Query(count), parameters); - return results; + results[i] = await ReadSingleRow(db); } - public async Task> LoadFortunesRows() + for (var i = 0; i < count; i++) { - List result; + var randomNumber = _random.Next(1, 10001); + parameters[$"@Rn_{i}"] = randomNumber; + parameters[$"@Id_{i}"] = results[i].Id; + + results[i].RandomNumber = randomNumber; + } + + await db.ExecuteAsync(BatchUpdateString.Query(count), parameters); + return results; + } + + public async Task> LoadFortunesRows() + { + List result; - using var db = _dbProviderFactory.CreateConnection(); + using var db = _dbProviderFactory.CreateConnection(); - db!.ConnectionString = _connectionString; + db!.ConnectionString = _connectionString; - // Note: don't need to open connection if only doing one thing; let dapper do it - result = (await db.QueryAsync("SELECT id, message FROM fortune")).AsList(); + // Note: don't need to open connection if only doing one thing; let dapper do it + result = (await db.QueryAsync("SELECT id, message FROM fortune")).AsList(); - result.Add(new Fortune(0, "Additional fortune added at request time.")); - result.Sort(FortuneSortComparison); + result.Add(new Fortune(0, "Additional fortune added at request time.")); + result.Sort(FortuneSortComparison); - return result; - } + return result; } } \ No newline at end of file diff --git a/src/BenchmarksApps/TechEmpower/Minimal/Minimal.csproj b/src/BenchmarksApps/TechEmpower/Minimal/Minimal.csproj index 74ecbd232..f005456fb 100644 --- a/src/BenchmarksApps/TechEmpower/Minimal/Minimal.csproj +++ b/src/BenchmarksApps/TechEmpower/Minimal/Minimal.csproj @@ -1,15 +1,17 @@ - + net7.0 enable enable latest + 38063504-d08c-495a-89c9-daaad2f60f31 + diff --git a/src/BenchmarksApps/TechEmpower/Minimal/Program.cs b/src/BenchmarksApps/TechEmpower/Minimal/Program.cs index dcfb68527..73920f036 100644 --- a/src/BenchmarksApps/TechEmpower/Minimal/Program.cs +++ b/src/BenchmarksApps/TechEmpower/Minimal/Program.cs @@ -1,6 +1,10 @@ +using System.Text.Encodings.Web; +using System.Text.Unicode; +using Microsoft.AspNetCore.Http.HttpResults; +using RazorSlices; using Minimal; using Minimal.Database; -using Minimal.Templates; +using Minimal.Models; var builder = WebApplication.CreateBuilder(args); @@ -27,9 +31,14 @@ app.MapGet("/db", async (Db db) => await db.LoadSingleQueryRow()); +var createFortunesTemplate = RazorSlice.ResolveSliceFactory("/Templates/Fortunes.cshtml"); +var htmlEncoder = CreateHtmlEncoder(); app.MapGet("/fortunes", async (HttpContext context, Db db) => { var fortunes = await db.LoadFortunesRows(); - await FortunesTemplate.Render(fortunes, context.Response); + var template = (RazorSliceHttpResult>)createFortunesTemplate(); + template.Model = fortunes; + template.HtmlEncoder = htmlEncoder; + return template; }); app.MapGet("/queries/{count}", async (Db db, int count) => await db.LoadMultipleQueriesRows(count)); @@ -40,3 +49,10 @@ app.Lifetime.ApplicationStopping.Register(() => Console.WriteLine("Application is shutting down...")); app.Run(); + +static HtmlEncoder CreateHtmlEncoder() +{ + var settings = new TextEncoderSettings(UnicodeRanges.BasicLatin, UnicodeRanges.Katakana, UnicodeRanges.Hiragana); + settings.AllowCharacter('\u2014'); // allow EM DASH through + return HtmlEncoder.Create(settings); +} \ No newline at end of file diff --git a/src/BenchmarksApps/TechEmpower/Minimal/Templates/Fortunes.cshtml b/src/BenchmarksApps/TechEmpower/Minimal/Templates/Fortunes.cshtml new file mode 100644 index 000000000..99bd370d3 --- /dev/null +++ b/src/BenchmarksApps/TechEmpower/Minimal/Templates/Fortunes.cshtml @@ -0,0 +1,2 @@ +@inherits RazorSliceHttpResult> +Fortunes@foreach (var item in Model){}
idmessage
@item.Id@item.Message
\ No newline at end of file diff --git a/src/BenchmarksApps/TechEmpower/Minimal/Templates/FortunesTemplate.cs b/src/BenchmarksApps/TechEmpower/Minimal/Templates/FortunesTemplate.cs deleted file mode 100644 index 9a3ad2f9b..000000000 --- a/src/BenchmarksApps/TechEmpower/Minimal/Templates/FortunesTemplate.cs +++ /dev/null @@ -1,26 +0,0 @@ -using System.Globalization; -using System.Text.Encodings.Web; -using Minimal.Models; - -namespace Minimal.Templates; - -public class FortunesTemplate -{ - public static async Task Render(List fortunes, HttpResponse response) - { - var htmlEncoder = HtmlEncoder.Default; - - await response.WriteAsync("Fortunes"); - - foreach (var item in fortunes) - { - await response.WriteAsync(""); - } - - await response.WriteAsync("
idmessage
"); - await response.WriteAsync(item.Id.ToString(CultureInfo.InvariantCulture)); - await response.WriteAsync(""); - await response.WriteAsync(htmlEncoder.Encode(item.Message)); - await response.WriteAsync("
"); - } -} \ No newline at end of file diff --git a/src/BenchmarksApps/TechEmpower/Minimal/Templates/_ViewImports.cshtml b/src/BenchmarksApps/TechEmpower/Minimal/Templates/_ViewImports.cshtml new file mode 100644 index 000000000..3fa54a0a3 --- /dev/null +++ b/src/BenchmarksApps/TechEmpower/Minimal/Templates/_ViewImports.cshtml @@ -0,0 +1,10 @@ +@inherits RazorSliceHttpResult + +@using System.Globalization; +@using Microsoft.AspNetCore.Razor; +@using Microsoft.AspNetCore.Http.HttpResults; +@using RazorSlices; +@using Minimal.Models; + +@tagHelperPrefix __disable_tagHelpers__: +@removeTagHelper *, Microsoft.AspNetCore.Mvc.Razor \ No newline at end of file diff --git a/src/BenchmarksApps/TechEmpower/Minimal/appsettings.json b/src/BenchmarksApps/TechEmpower/Minimal/appsettings.json index 10f68b8c8..161c14fe7 100644 --- a/src/BenchmarksApps/TechEmpower/Minimal/appsettings.json +++ b/src/BenchmarksApps/TechEmpower/Minimal/appsettings.json @@ -5,5 +5,6 @@ "Microsoft.AspNetCore": "Warning" } }, - "AllowedHosts": "*" + "AllowedHosts": "*", + "ConnectionString": "This should be set in user secrets or environment config" } diff --git a/src/BenchmarksApps/TechEmpower/Minimal/minimal.benchmarks.yml b/src/BenchmarksApps/TechEmpower/Minimal/minimal.benchmarks.yml index 1d7837aed..b8f2da357 100644 --- a/src/BenchmarksApps/TechEmpower/Minimal/minimal.benchmarks.yml +++ b/src/BenchmarksApps/TechEmpower/Minimal/minimal.benchmarks.yml @@ -10,7 +10,7 @@ jobs: source: repository: https://github.com/aspnet/benchmarks.git branchOrCommit: main - project: src/BenchmarksApps/TechEmpower/Minimal.csproj + project: src/BenchmarksApps/TechEmpower/Minimal/Minimal.csproj readyStateText: Application started. arguments: "--urls {{serverScheme}}://{{serverAddress}}:{{serverPort}}" variables: diff --git a/src/BenchmarksApps/TechEmpower/PlatformBenchmarks/BenchmarkApplication.Fortunes.cs b/src/BenchmarksApps/TechEmpower/PlatformBenchmarks/BenchmarkApplication.Fortunes.cs index e1901717c..d181de2ca 100644 --- a/src/BenchmarksApps/TechEmpower/PlatformBenchmarks/BenchmarkApplication.Fortunes.cs +++ b/src/BenchmarksApps/TechEmpower/PlatformBenchmarks/BenchmarkApplication.Fortunes.cs @@ -2,124 +2,89 @@ // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System; -using System.Collections.Generic; -using System.Diagnostics; using System.IO.Pipelines; -using System.Text.Encodings.Web; +using System.Runtime.CompilerServices; +using System.Threading; using System.Threading.Tasks; +using RazorSlices; namespace PlatformBenchmarks { public partial class BenchmarkApplication { - private readonly static AsciiString _fortunesPreamble = - _http11OK + - _headerServer + _crlf + - _headerContentTypeHtml + _crlf + - _headerContentLength; - private async Task FortunesRaw(PipeWriter pipeWriter) { - OutputFortunes(pipeWriter, await RawDb.LoadFortunesRows()); + await OutputFortunes(pipeWriter, await RawDb.LoadFortunesRows(), FortunesTemplateFactory); } private async Task FortunesDapper(PipeWriter pipeWriter) { - OutputFortunes(pipeWriter, await DapperDb.LoadFortunesRows()); + await OutputFortunes(pipeWriter, await DapperDb.LoadFortunesRows(), FortunesDapperTemplateFactory); } private async Task FortunesEf(PipeWriter pipeWriter) { - OutputFortunes(pipeWriter, await EfDb.LoadFortunesRows()); + await OutputFortunes(pipeWriter, await EfDb.LoadFortunesRows(), FortunesEfTemplateFactory); } - private void OutputFortunes(PipeWriter pipeWriter, List model) + private ValueTask OutputFortunes(PipeWriter pipeWriter, TModel model, SliceFactory templateFactory) { - var writer = GetWriter(pipeWriter, sizeHint: 1600); // in reality it's 1361 - - writer.Write(_fortunesPreamble); - - var lengthWriter = writer; - writer.Write(_contentLengthGap); - - // Date header - writer.Write(DateHeader.HeaderBytes); - - var bodyStart = writer.Buffered; - // Body - writer.Write(_fortunesTableStart); - foreach (var item in model) + // Render headers + var preamble = """ + HTTP/1.1 200 OK + Server: K + Content-Type: text/html; charset=utf-8 + Transfer-Encoding: chunked + """u8; + var headersLength = preamble.Length + DateHeader.HeaderBytes.Length; + var headersSpan = pipeWriter.GetSpan(headersLength); + preamble.CopyTo(headersSpan); + DateHeader.HeaderBytes.CopyTo(headersSpan[preamble.Length..]); + pipeWriter.Advance(headersLength); + + // Render body + var template = (RazorSlice)templateFactory(); + template.Model = model; + // Kestrel PipeWriter span size is 4K, headers above already written to first span & template output is ~1350 bytes, + // so 2K chunk size should result in only a single span and chunk being used. + var chunkedWriter = GetChunkedWriter(pipeWriter, chunkSize: 2048); + var renderTask = template.RenderAsync(chunkedWriter, GetFlushWrapper(pipeWriter), HtmlEncoder); + + if (renderTask.IsCompletedSuccessfully) { - writer.Write(_fortunesRowStart); - writer.WriteNumeric((uint)item.Id); - writer.Write(_fortunesColumn); - HtmlEncoder.EncodeUtf8(item.Message.AsSpan(), writer.Span, out var bytesConsumed, out var bytesWritten, isFinalBlock: true); - Debug.Assert(bytesConsumed == item.Message.Length, "Not enough remaining space in the buffer"); - writer.Advance(bytesWritten); - writer.Write(_fortunesRowEnd); + EndTemplateRendering(chunkedWriter, template); + return ValueTask.CompletedTask; } - writer.Write(_fortunesTableEnd); - lengthWriter.WriteNumeric((uint)(writer.Buffered - bodyStart)); - writer.Commit(); + return AwaitTemplateRenderTask(renderTask, chunkedWriter, template); } - private void OutputFortunes(PipeWriter pipeWriter, List model) - { - var writer = GetWriter(pipeWriter, sizeHint: 1600); // in reality it's 1361 - - writer.Write(_fortunesPreamble); - - var lengthWriter = writer; - writer.Write(_contentLengthGap); - - // Date header - writer.Write(DateHeader.HeaderBytes); + private static Func GetFlushWrapper(PipeWriter pipeWriter) => + ct => AsValueTask(pipeWriter.FlushAsync(ct)); - var bodyStart = writer.Buffered; - // Body - writer.Write(_fortunesTableStart); - foreach (var item in model) + private static ValueTask AsValueTask(ValueTask valueTask) + { + if (valueTask.IsCompletedSuccessfully) { - writer.Write(_fortunesRowStart); - writer.WriteNumeric((uint)item.Id); - writer.Write(_fortunesColumn); - writer.WriteUtf8String(HtmlEncoder.Encode(item.Message)); - writer.Write(_fortunesRowEnd); + var _ = valueTask.GetAwaiter().GetResult(); + return default; } - writer.Write(_fortunesTableEnd); - lengthWriter.WriteNumeric((uint)(writer.Buffered - bodyStart)); - writer.Commit(); + return new ValueTask(valueTask.AsTask()); } - private void OutputFortunes(PipeWriter pipeWriter, List model) + private static async ValueTask AwaitTemplateRenderTask(ValueTask renderTask, ChunkedBufferWriter chunkedWriter, RazorSlice template) { - var writer = GetWriter(pipeWriter, sizeHint: 1600); // in reality it's 1361 - - writer.Write(_fortunesPreamble); - - var lengthWriter = writer; - writer.Write(_contentLengthGap); - - // Date header - writer.Write(DateHeader.HeaderBytes); - - var bodyStart = writer.Buffered; - // Body - writer.Write(_fortunesTableStart); - foreach (var item in model) - { - writer.Write(_fortunesRowStart); - writer.WriteNumeric((uint)item.Id); - writer.Write(_fortunesColumn); - writer.WriteUtf8String(HtmlEncoder.Encode(item.Message)); - writer.Write(_fortunesRowEnd); - } - writer.Write(_fortunesTableEnd); - lengthWriter.WriteNumeric((uint)(writer.Buffered - bodyStart)); + await renderTask; + EndTemplateRendering(chunkedWriter, template); + } - writer.Commit(); + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static void EndTemplateRendering(ChunkedBufferWriter chunkedWriter, RazorSlice template) + { + chunkedWriter.End(); + ReturnChunkedWriter(chunkedWriter); + template.Dispose(); } } } diff --git a/src/BenchmarksApps/TechEmpower/PlatformBenchmarks/BenchmarkApplication.HttpConnection.cs b/src/BenchmarksApps/TechEmpower/PlatformBenchmarks/BenchmarkApplication.HttpConnection.cs index 17c0c7821..91ee58c37 100644 --- a/src/BenchmarksApps/TechEmpower/PlatformBenchmarks/BenchmarkApplication.HttpConnection.cs +++ b/src/BenchmarksApps/TechEmpower/PlatformBenchmarks/BenchmarkApplication.HttpConnection.cs @@ -287,6 +287,17 @@ private enum State private static BufferWriter GetWriter(PipeWriter pipeWriter, int sizeHint) => new BufferWriter(new WriterAdapter(pipeWriter), sizeHint); + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static ChunkedBufferWriter GetChunkedWriter(PipeWriter pipeWriter, int chunkSize) + { + var writer = ChunkedWriterPool.Get(); + writer.SetOutput(new WriterAdapter(pipeWriter), chunkSize); + return writer; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static void ReturnChunkedWriter(ChunkedBufferWriter writer) => ChunkedWriterPool.Return(writer); + private struct WriterAdapter : IBufferWriter { public PipeWriter Writer; diff --git a/src/BenchmarksApps/TechEmpower/PlatformBenchmarks/BenchmarkApplication.cs b/src/BenchmarksApps/TechEmpower/PlatformBenchmarks/BenchmarkApplication.cs index dafeb29b7..cdc5c7b8b 100644 --- a/src/BenchmarksApps/TechEmpower/PlatformBenchmarks/BenchmarkApplication.cs +++ b/src/BenchmarksApps/TechEmpower/PlatformBenchmarks/BenchmarkApplication.cs @@ -3,11 +3,14 @@ using System; using System.Buffers.Text; +using System.Collections.Generic; using System.IO.Pipelines; using System.Text.Json; using System.Text.Json.Serialization; using System.Threading.Tasks; using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http; +using Microsoft.Extensions.ObjectPool; +using RazorSlices; namespace PlatformBenchmarks { @@ -25,7 +28,6 @@ public partial class BenchmarkApplication private readonly static AsciiString _headerContentLengthZero = "Content-Length: 0"; private readonly static AsciiString _headerContentTypeText = "Content-Type: text/plain"; private readonly static AsciiString _headerContentTypeJson = "Content-Type: application/json"; - private readonly static AsciiString _headerContentTypeHtml = "Content-Type: text/html; charset=UTF-8"; private readonly static AsciiString _dbPreamble = _http11OK + @@ -34,18 +36,30 @@ public partial class BenchmarkApplication _headerContentLength; private readonly static AsciiString _plainTextBody = "Hello, World!"; - - private readonly static AsciiString _fortunesTableStart = "Fortunes"; - private readonly static AsciiString _fortunesRowStart = ""; - private readonly static AsciiString _fortunesTableEnd = "
idmessage
"; - private readonly static AsciiString _fortunesColumn = ""; - private readonly static AsciiString _fortunesRowEnd = "
"; private readonly static AsciiString _contentLengthGap = new string(' ', 4); public static RawDb RawDb { get; set; } public static DapperDb DapperDb { get; set; } public static EfDb EfDb { get; set; } + private static readonly DefaultObjectPool> ChunkedWriterPool + = new(new ChunkedWriterObjectPolicy()); + + private sealed class ChunkedWriterObjectPolicy : IPooledObjectPolicy> + { + public ChunkedBufferWriter Create() => new(); + + public bool Return(ChunkedBufferWriter writer) + { + writer.Reset(); + return true; + } + } + + private readonly static SliceFactory FortunesTemplateFactory = RazorSlice.ResolveSliceFactory>("/Templates/Fortunes.cshtml"); + private readonly static SliceFactory FortunesDapperTemplateFactory = RazorSlice.ResolveSliceFactory>("/Templates/FortunesDapper.cshtml"); + private readonly static SliceFactory FortunesEfTemplateFactory = RazorSlice.ResolveSliceFactory>("/Templates/FortunesEf.cshtml"); + [ThreadStatic] private static Utf8JsonWriter t_writer; diff --git a/src/BenchmarksApps/TechEmpower/PlatformBenchmarks/ChunkedBufferWriter.cs b/src/BenchmarksApps/TechEmpower/PlatformBenchmarks/ChunkedBufferWriter.cs new file mode 100644 index 000000000..a8bd65dc7 --- /dev/null +++ b/src/BenchmarksApps/TechEmpower/PlatformBenchmarks/ChunkedBufferWriter.cs @@ -0,0 +1,219 @@ +using System; +using System.Buffers; +using System.Buffers.Text; +using System.Diagnostics; +using System.Runtime.CompilerServices; + +namespace PlatformBenchmarks; + +internal sealed class ChunkedBufferWriter : IBufferWriter where TWriter : IBufferWriter +{ + private const int DefaultChunkSizeHint = 4096; + private static readonly StandardFormat DefaultHexFormat = GetHexFormat(DefaultChunkSizeHint); + + private TWriter _output; + private int _chunkSizeHint; + private StandardFormat _hexFormat = DefaultHexFormat; + private Memory _currentFullChunk; + private Memory _currentChunk; + private int _buffered; + private bool _ended = false; + + public Memory Memory => _currentChunk; + + public TWriter Output => _output; + + public int Buffered => _buffered; + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void SetOutput(TWriter output, int chunkSizeHint = DefaultChunkSizeHint) + { + _buffered = 0; + _chunkSizeHint = chunkSizeHint; + _output = output; + + StartNewChunk(chunkSizeHint, isFirst: true); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void Reset() + { + _buffered = 0; + _output = default; + _ended = false; + _hexFormat = DefaultHexFormat; + _currentFullChunk = default; + _currentChunk = default; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void Advance(int count) + { + ThrowIfEnded(); + + _buffered += count; + _currentChunk = _currentChunk[count..]; + } + + public Memory GetMemory(int sizeHint = 0) + { + ThrowIfEnded(); + + if (_currentChunk.Length <= sizeHint) + { + EnsureMore(sizeHint); + } + return _currentChunk; + } + + public Span GetSpan(int sizeHint = 0) => GetMemory(sizeHint).Span; + + public void End() + { + ThrowIfEnded(); + + CommitCurrentChunk(isFinal: true); + + // Write out chunking terminator + var terminator = "0\r\n\r\n"u8; + var span = _output.GetSpan(terminator.Length); + terminator.CopyTo(span); + _output.Advance(terminator.Length); + + _ended = true; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static StandardFormat GetHexFormat(int maxValue) + { + var hexDigitCount = CountHexDigits(maxValue); + + return new StandardFormat('X', (byte)hexDigitCount); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static int CountHexDigits(int n) => n <= 16 ? 1 : (int)Math.Ceiling(Math.Log(n, 16)); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private void StartNewChunk(int sizeHint, bool isFirst = false) + { + // Header is like: + // 520\r\n + + var oldFullChunkHexLength = -1; + if (!isFirst) + { + oldFullChunkHexLength = CountHexDigits(_currentFullChunk.Length); + } + _currentFullChunk = _output.GetMemory(Math.Max(_chunkSizeHint, sizeHint)); + var newFullChunkHexLength = CountHexDigits(_currentFullChunk.Length); + + var currentFullChunkSpan = _currentFullChunk.Span; + + // Write space for HEX digits + currentFullChunkSpan[..newFullChunkHexLength].Fill(48); // 48 == '0' + + // Write header terminator + var terminator = "\r\n"u8; + terminator.CopyTo(currentFullChunkSpan[newFullChunkHexLength..]); + var chunkHeaderLength = newFullChunkHexLength + terminator.Length; + _currentChunk = _currentFullChunk[chunkHeaderLength..]; + + if (!isFirst && oldFullChunkHexLength != newFullChunkHexLength) + { + // Update HEX format if changed + _hexFormat = GetHexFormat(_currentFullChunk.Length); + } + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private void CommitCurrentChunk(bool isFinal = false, int sizeHint = 0) + { + ThrowIfEnded(); + + var contentLength = _buffered; + if (contentLength > 0) + { + // Update the chunk header + var maxLength = CountHexDigits(contentLength); + var span = _currentFullChunk.Span; + if (!Utf8Formatter.TryFormat(contentLength, span, out var bytesWritten, _hexFormat)) + { + throw new NotSupportedException("Chunk size too large"); + } + Debug.Assert(maxLength == bytesWritten, "HEX formatting math problem."); + var headerLength = maxLength + 2; + + // Total chunk length: content length as HEX string + \r\n + content + \r\n + var offset = headerLength + contentLength; + var chunkTotalLength = offset + 2; + + // Write out the chunk terminator + "\r\n"u8.CopyTo(span[offset..]); + + _output.Advance(chunkTotalLength); + _buffered = 0; + + if (!isFinal) + { + StartNewChunk(sizeHint); + } + } + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void Write(ReadOnlySpan source) + { + ThrowIfEnded(); + + if (_currentChunk.Length >= source.Length) + { + source.CopyTo(_currentChunk.Span); + Advance(source.Length); + } + else + { + WriteMultiBuffer(source); + } + } + + [MethodImpl(MethodImplOptions.NoInlining)] + private void EnsureMore(int count = 0) + { + if ((_buffered + count) > _currentChunk.Length) + { + if (_buffered > 0) + { + CommitCurrentChunk(isFinal: false, count); + } + else + { + StartNewChunk(count); + } + } + } + + private void WriteMultiBuffer(ReadOnlySpan source) + { + while (source.Length > 0) + { + if (_currentChunk.Length == 0) + { + EnsureMore(); + } + + var writable = Math.Min(source.Length, _currentChunk.Length); + source[..writable].CopyTo(_currentChunk.Span); + source = source[writable..]; + Advance(writable); + } + } + + private void ThrowIfEnded() + { + if (_ended) + { + throw new InvalidOperationException("Cannot use the writer after calling End()."); + } + } +} \ No newline at end of file diff --git a/src/BenchmarksApps/TechEmpower/PlatformBenchmarks/Data/Fortune.cs b/src/BenchmarksApps/TechEmpower/PlatformBenchmarks/Data/Fortune.cs index 5a2d7c5a3..004f73e3c 100644 --- a/src/BenchmarksApps/TechEmpower/PlatformBenchmarks/Data/Fortune.cs +++ b/src/BenchmarksApps/TechEmpower/PlatformBenchmarks/Data/Fortune.cs @@ -10,16 +10,16 @@ namespace PlatformBenchmarks public Fortune(int id, byte[] message) { Id = id; - Message = message; + MessageUtf8 = message; } public int Id { get; } - public byte[] Message { get; } + public byte[] MessageUtf8 { get; } public int CompareTo(object obj) => throw new InvalidOperationException("The non-generic CompareTo should not be used"); // Performance critical, using culture insensitive comparison - public int CompareTo(Fortune other) => Message.AsSpan().SequenceCompareTo(other.Message.AsSpan()); + public int CompareTo(Fortune other) => MessageUtf8.AsSpan().SequenceCompareTo(other.MessageUtf8.AsSpan()); } } diff --git a/src/BenchmarksApps/TechEmpower/PlatformBenchmarks/Data/RawDb.cs b/src/BenchmarksApps/TechEmpower/PlatformBenchmarks/Data/RawDb.cs index 55132e74b..d35f44dab 100644 --- a/src/BenchmarksApps/TechEmpower/PlatformBenchmarks/Data/RawDb.cs +++ b/src/BenchmarksApps/TechEmpower/PlatformBenchmarks/Data/RawDb.cs @@ -241,7 +241,8 @@ public async Task LoadMultipleUpdatesRows(int count) public async Task> LoadFortunesRows() { - var result = new List(20); + // Benchmark requirements explicitly prohibit pre-initializing the list size + var result = new List(); using (var db = CreateConnection()) { diff --git a/src/BenchmarksApps/TechEmpower/PlatformBenchmarks/PlatformBenchmarks.csproj b/src/BenchmarksApps/TechEmpower/PlatformBenchmarks/PlatformBenchmarks.csproj index 090ef4615..cb8c5b938 100644 --- a/src/BenchmarksApps/TechEmpower/PlatformBenchmarks/PlatformBenchmarks.csproj +++ b/src/BenchmarksApps/TechEmpower/PlatformBenchmarks/PlatformBenchmarks.csproj @@ -5,6 +5,8 @@ Exe true true + true + 38063504-d08c-495a-89c9-daaad2f60f31 @@ -17,24 +19,7 @@ - - - - - - - - - - - - - - - - - - + diff --git a/src/BenchmarksApps/TechEmpower/PlatformBenchmarks/Program.cs b/src/BenchmarksApps/TechEmpower/PlatformBenchmarks/Program.cs index 35889e774..1c4d1feb0 100644 --- a/src/BenchmarksApps/TechEmpower/PlatformBenchmarks/Program.cs +++ b/src/BenchmarksApps/TechEmpower/PlatformBenchmarks/Program.cs @@ -40,7 +40,14 @@ public static async Task Main(string[] args) var config = (IConfiguration)host.Services.GetService(typeof(IConfiguration)); BatchUpdateString.DatabaseServer = config.Get().Database; #if DATABASE - await BenchmarkApplication.RawDb.PopulateCache(); + try + { + await BenchmarkApplication.RawDb.PopulateCache(); + } + catch (Exception ex) + { + Console.WriteLine($"Error trying to populate database cache: {ex}"); + } #endif await host.RunAsync(); } @@ -52,6 +59,9 @@ public static IWebHost BuildWebHost(string[] args) var config = new ConfigurationBuilder() .AddJsonFile("appsettings.json") +#if DEBUG + .AddUserSecrets() +#endif .AddEnvironmentVariables() .AddEnvironmentVariables(prefix: "ASPNETCORE_") .AddCommandLine(args) diff --git a/src/BenchmarksApps/TechEmpower/PlatformBenchmarks/Templates/Fortunes.cshtml b/src/BenchmarksApps/TechEmpower/PlatformBenchmarks/Templates/Fortunes.cshtml new file mode 100644 index 000000000..b1f8e0a81 --- /dev/null +++ b/src/BenchmarksApps/TechEmpower/PlatformBenchmarks/Templates/Fortunes.cshtml @@ -0,0 +1,2 @@ +@inherits RazorSlice> +Fortunes@foreach (var item in Model){}
idmessage
@item.Id@item.MessageUtf8
\ No newline at end of file diff --git a/src/BenchmarksApps/TechEmpower/PlatformBenchmarks/Templates/FortunesDapper.cshtml b/src/BenchmarksApps/TechEmpower/PlatformBenchmarks/Templates/FortunesDapper.cshtml new file mode 100644 index 000000000..95c9df923 --- /dev/null +++ b/src/BenchmarksApps/TechEmpower/PlatformBenchmarks/Templates/FortunesDapper.cshtml @@ -0,0 +1,2 @@ +@inherits RazorSlice> +Fortunes@foreach (var item in Model){}
idmessage
@item.Id@item.Message
\ No newline at end of file diff --git a/src/BenchmarksApps/TechEmpower/PlatformBenchmarks/Templates/FortunesEf.cshtml b/src/BenchmarksApps/TechEmpower/PlatformBenchmarks/Templates/FortunesEf.cshtml new file mode 100644 index 000000000..423da4bc4 --- /dev/null +++ b/src/BenchmarksApps/TechEmpower/PlatformBenchmarks/Templates/FortunesEf.cshtml @@ -0,0 +1,2 @@ +@inherits RazorSlice> +Fortunes@foreach (var item in Model){}
idmessage
@item.Id@item.Message
\ No newline at end of file diff --git a/src/BenchmarksApps/TechEmpower/PlatformBenchmarks/Templates/_ViewImports.cshtml b/src/BenchmarksApps/TechEmpower/PlatformBenchmarks/Templates/_ViewImports.cshtml new file mode 100644 index 000000000..7cda0a301 --- /dev/null +++ b/src/BenchmarksApps/TechEmpower/PlatformBenchmarks/Templates/_ViewImports.cshtml @@ -0,0 +1,9 @@ +@inherits RazorSlice + +@using System.Globalization; +@using Microsoft.AspNetCore.Razor; +@using RazorSlices; +@using PlatformBenchmarks; + +@tagHelperPrefix __disable_tagHelpers__: +@removeTagHelper *, Microsoft.AspNetCore.Mvc.Razor \ No newline at end of file diff --git a/src/BenchmarksApps/TechEmpower/PlatformBenchmarks/appsettings.json b/src/BenchmarksApps/TechEmpower/PlatformBenchmarks/appsettings.json index d7a356382..4b8008a88 100644 --- a/src/BenchmarksApps/TechEmpower/PlatformBenchmarks/appsettings.json +++ b/src/BenchmarksApps/TechEmpower/PlatformBenchmarks/appsettings.json @@ -1,3 +1,4 @@ { - "ConnectionString": "Server=(localdb)\\mssqllocaldb;Database=aspnetcore-Benchmarks;Trusted_Connection=True;MultipleActiveResultSets=true" + "ConnectionString": "This should be set in user secrets or environment config", + "Database": "PostgreSql" } From a9dc02751c70b8cdb3824fb779cea324c77feae0 Mon Sep 17 00:00:00 2001 From: Damian Edwards Date: Fri, 10 Mar 2023 14:31:38 -0800 Subject: [PATCH 2/9] PR feedback --- .../TechEmpower/Minimal/Minimal.csproj | 2 +- .../BenchmarkApplication.Fortunes.cs | 5 +-- .../PlatformBenchmarks/ChunkedBufferWriter.cs | 39 ++++++++++++------- 3 files changed, 28 insertions(+), 18 deletions(-) diff --git a/src/BenchmarksApps/TechEmpower/Minimal/Minimal.csproj b/src/BenchmarksApps/TechEmpower/Minimal/Minimal.csproj index f005456fb..47f139e51 100644 --- a/src/BenchmarksApps/TechEmpower/Minimal/Minimal.csproj +++ b/src/BenchmarksApps/TechEmpower/Minimal/Minimal.csproj @@ -11,7 +11,7 @@ - +
diff --git a/src/BenchmarksApps/TechEmpower/PlatformBenchmarks/BenchmarkApplication.Fortunes.cs b/src/BenchmarksApps/TechEmpower/PlatformBenchmarks/BenchmarkApplication.Fortunes.cs index d181de2ca..33fd92db2 100644 --- a/src/BenchmarksApps/TechEmpower/PlatformBenchmarks/BenchmarkApplication.Fortunes.cs +++ b/src/BenchmarksApps/TechEmpower/PlatformBenchmarks/BenchmarkApplication.Fortunes.cs @@ -48,7 +48,7 @@ private ValueTask OutputFortunes(PipeWriter pipeWriter, TModel model, Sl // Kestrel PipeWriter span size is 4K, headers above already written to first span & template output is ~1350 bytes, // so 2K chunk size should result in only a single span and chunk being used. var chunkedWriter = GetChunkedWriter(pipeWriter, chunkSize: 2048); - var renderTask = template.RenderAsync(chunkedWriter, GetFlushWrapper(pipeWriter), HtmlEncoder); + var renderTask = template.RenderAsync(chunkedWriter, null, HtmlEncoder); if (renderTask.IsCompletedSuccessfully) { @@ -59,9 +59,6 @@ private ValueTask OutputFortunes(PipeWriter pipeWriter, TModel model, Sl return AwaitTemplateRenderTask(renderTask, chunkedWriter, template); } - private static Func GetFlushWrapper(PipeWriter pipeWriter) => - ct => AsValueTask(pipeWriter.FlushAsync(ct)); - private static ValueTask AsValueTask(ValueTask valueTask) { if (valueTask.IsCompletedSuccessfully) diff --git a/src/BenchmarksApps/TechEmpower/PlatformBenchmarks/ChunkedBufferWriter.cs b/src/BenchmarksApps/TechEmpower/PlatformBenchmarks/ChunkedBufferWriter.cs index a8bd65dc7..7b3389631 100644 --- a/src/BenchmarksApps/TechEmpower/PlatformBenchmarks/ChunkedBufferWriter.cs +++ b/src/BenchmarksApps/TechEmpower/PlatformBenchmarks/ChunkedBufferWriter.cs @@ -74,12 +74,6 @@ public void End() CommitCurrentChunk(isFinal: true); - // Write out chunking terminator - var terminator = "0\r\n\r\n"u8; - var span = _output.GetSpan(terminator.Length); - terminator.CopyTo(span); - _output.Advance(terminator.Length); - _ended = true; } @@ -132,6 +126,7 @@ private void CommitCurrentChunk(bool isFinal = false, int sizeHint = 0) ThrowIfEnded(); var contentLength = _buffered; + if (contentLength > 0) { // Update the chunk header @@ -145,19 +140,37 @@ private void CommitCurrentChunk(bool isFinal = false, int sizeHint = 0) var headerLength = maxLength + 2; // Total chunk length: content length as HEX string + \r\n + content + \r\n - var offset = headerLength + contentLength; - var chunkTotalLength = offset + 2; + var spanOffset = headerLength + contentLength; + var chunkTotalLength = spanOffset + 2; // Write out the chunk terminator - "\r\n"u8.CopyTo(span[offset..]); - - _output.Advance(chunkTotalLength); - _buffered = 0; - + "\r\n"u8.CopyTo(span[spanOffset..]); + spanOffset = chunkTotalLength; + if (!isFinal) { + _output.Advance(chunkTotalLength); StartNewChunk(sizeHint); } + else + { + // Write out final chunk (zero-length chunk) + var terminator = "0\r\n\r\n"u8; + if ((spanOffset + terminator.Length) <= span.Length) + { + // There's space for the final chunk in the current span + terminator.CopyTo(span[spanOffset..]); + _output.Advance(chunkTotalLength + terminator.Length); + } + else + { + // Final chunk doesn't fit in current span so just write it directly after advancing the writer + _output.Advance(chunkTotalLength); + _output.Write(terminator); + } + } + + _buffered = 0; } } From a3c001f7da52de8e8578bc57780c472f8f8302ed Mon Sep 17 00:00:00 2001 From: Damian Edwards Date: Fri, 10 Mar 2023 16:49:44 -0800 Subject: [PATCH 3/9] Parameter rename --- .../PlatformBenchmarks/BenchmarkApplication.Fortunes.cs | 2 +- .../PlatformBenchmarks/BenchmarkApplication.HttpConnection.cs | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/BenchmarksApps/TechEmpower/PlatformBenchmarks/BenchmarkApplication.Fortunes.cs b/src/BenchmarksApps/TechEmpower/PlatformBenchmarks/BenchmarkApplication.Fortunes.cs index 33fd92db2..1f8941f62 100644 --- a/src/BenchmarksApps/TechEmpower/PlatformBenchmarks/BenchmarkApplication.Fortunes.cs +++ b/src/BenchmarksApps/TechEmpower/PlatformBenchmarks/BenchmarkApplication.Fortunes.cs @@ -47,7 +47,7 @@ private ValueTask OutputFortunes(PipeWriter pipeWriter, TModel model, Sl template.Model = model; // Kestrel PipeWriter span size is 4K, headers above already written to first span & template output is ~1350 bytes, // so 2K chunk size should result in only a single span and chunk being used. - var chunkedWriter = GetChunkedWriter(pipeWriter, chunkSize: 2048); + var chunkedWriter = GetChunkedWriter(pipeWriter, chunkSizeHint: 2048); var renderTask = template.RenderAsync(chunkedWriter, null, HtmlEncoder); if (renderTask.IsCompletedSuccessfully) diff --git a/src/BenchmarksApps/TechEmpower/PlatformBenchmarks/BenchmarkApplication.HttpConnection.cs b/src/BenchmarksApps/TechEmpower/PlatformBenchmarks/BenchmarkApplication.HttpConnection.cs index 91ee58c37..e9627a316 100644 --- a/src/BenchmarksApps/TechEmpower/PlatformBenchmarks/BenchmarkApplication.HttpConnection.cs +++ b/src/BenchmarksApps/TechEmpower/PlatformBenchmarks/BenchmarkApplication.HttpConnection.cs @@ -288,10 +288,10 @@ private static BufferWriter GetWriter(PipeWriter pipeWriter, int => new BufferWriter(new WriterAdapter(pipeWriter), sizeHint); [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static ChunkedBufferWriter GetChunkedWriter(PipeWriter pipeWriter, int chunkSize) + private static ChunkedBufferWriter GetChunkedWriter(PipeWriter pipeWriter, int chunkSizeHint) { var writer = ChunkedWriterPool.Get(); - writer.SetOutput(new WriterAdapter(pipeWriter), chunkSize); + writer.SetOutput(new WriterAdapter(pipeWriter), chunkSizeHint); return writer; } From 4ca1dd0eba7459a5b670957b9036a074b0af0703 Mon Sep 17 00:00:00 2001 From: Damian Edwards Date: Sun, 12 Mar 2023 18:45:53 -0700 Subject: [PATCH 4/9] PR feedback --- .../TechEmpower/Minimal/Database/Db.cs | 18 ++---------------- .../PlatformBenchmarks/BenchmarkApplication.cs | 6 +++--- .../PlatformBenchmarks/ChunkedBufferWriter.cs | 17 ++++++++++------- 3 files changed, 15 insertions(+), 26 deletions(-) diff --git a/src/BenchmarksApps/TechEmpower/Minimal/Database/Db.cs b/src/BenchmarksApps/TechEmpower/Minimal/Database/Db.cs index fd1c16501..0a395e67a 100644 --- a/src/BenchmarksApps/TechEmpower/Minimal/Database/Db.cs +++ b/src/BenchmarksApps/TechEmpower/Minimal/Database/Db.cs @@ -39,14 +39,7 @@ static Task ReadSingleRow(DbConnection db) public async Task LoadMultipleQueriesRows(int count) { - if (count <= 0) - { - count = 1; - } - else if (count > 500) - { - count = 500; - } + count = Math.Clamp(count, 1, 500); var results = new World[count]; using var db = _dbProviderFactory.CreateConnection(); @@ -64,14 +57,7 @@ public async Task LoadMultipleQueriesRows(int count) public async Task LoadMultipleUpdatesRows(int count) { - if (count <= 0) - { - count = 1; - } - else if (count > 500) - { - count = 500; - } + count = Math.Clamp(count, 1, 500); var parameters = new Dictionary(); diff --git a/src/BenchmarksApps/TechEmpower/PlatformBenchmarks/BenchmarkApplication.cs b/src/BenchmarksApps/TechEmpower/PlatformBenchmarks/BenchmarkApplication.cs index cdc5c7b8b..d84ac9842 100644 --- a/src/BenchmarksApps/TechEmpower/PlatformBenchmarks/BenchmarkApplication.cs +++ b/src/BenchmarksApps/TechEmpower/PlatformBenchmarks/BenchmarkApplication.cs @@ -171,13 +171,13 @@ private void ProcessRequest(ref BufferWriter writer) private static int ParseQueries(ReadOnlySpan parameter) { - if (!Utf8Parser.TryParse(parameter, out int queries, out _) || queries < 1) + if (!Utf8Parser.TryParse(parameter, out int queries, out _)) { queries = 1; } - else if (queries > 500) + else { - queries = 500; + queries = Math.Clamp(queries, 1, 500); } return queries; diff --git a/src/BenchmarksApps/TechEmpower/PlatformBenchmarks/ChunkedBufferWriter.cs b/src/BenchmarksApps/TechEmpower/PlatformBenchmarks/ChunkedBufferWriter.cs index 7b3389631..5ed1ea616 100644 --- a/src/BenchmarksApps/TechEmpower/PlatformBenchmarks/ChunkedBufferWriter.cs +++ b/src/BenchmarksApps/TechEmpower/PlatformBenchmarks/ChunkedBufferWriter.cs @@ -2,13 +2,14 @@ using System.Buffers; using System.Buffers.Text; using System.Diagnostics; +using System.Numerics; using System.Runtime.CompilerServices; namespace PlatformBenchmarks; internal sealed class ChunkedBufferWriter : IBufferWriter where TWriter : IBufferWriter { - private const int DefaultChunkSizeHint = 4096; + private const int DefaultChunkSizeHint = 2048; private static readonly StandardFormat DefaultHexFormat = GetHexFormat(DefaultChunkSizeHint); private TWriter _output; @@ -86,7 +87,7 @@ private static StandardFormat GetHexFormat(int maxValue) } [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static int CountHexDigits(int n) => n <= 16 ? 1 : (int)Math.Ceiling(Math.Log(n, 16)); + private static int CountHexDigits(int n) => n <= 16 ? 1 : (BitOperations.Log2((uint)n) >> 2) + 1; [MethodImpl(MethodImplOptions.AggressiveInlining)] private void StartNewChunk(int sizeHint, bool isFirst = false) @@ -113,7 +114,7 @@ private void StartNewChunk(int sizeHint, bool isFirst = false) var chunkHeaderLength = newFullChunkHexLength + terminator.Length; _currentChunk = _currentFullChunk[chunkHeaderLength..]; - if (!isFirst && oldFullChunkHexLength != newFullChunkHexLength) + if ((!isFirst && oldFullChunkHexLength != newFullChunkHexLength) || (isFirst && DefaultChunkSizeHint != _chunkSizeHint)) { // Update HEX format if changed _hexFormat = GetHexFormat(_currentFullChunk.Length); @@ -130,19 +131,21 @@ private void CommitCurrentChunk(bool isFinal = false, int sizeHint = 0) if (contentLength > 0) { // Update the chunk header - var maxLength = CountHexDigits(contentLength); + var chunkLengthHexDigitsLength = CountHexDigits(contentLength); var span = _currentFullChunk.Span; if (!Utf8Formatter.TryFormat(contentLength, span, out var bytesWritten, _hexFormat)) { throw new NotSupportedException("Chunk size too large"); } - Debug.Assert(maxLength == bytesWritten, "HEX formatting math problem."); - var headerLength = maxLength + 2; + Debug.Assert(chunkLengthHexDigitsLength == bytesWritten, "HEX formatting math problem."); + var headerLength = chunkLengthHexDigitsLength + 2; // Total chunk length: content length as HEX string + \r\n + content + \r\n var spanOffset = headerLength + contentLength; var chunkTotalLength = spanOffset + 2; + Debug.Assert(span.Length >= chunkTotalLength, "Bad chunk size calculation."); + // Write out the chunk terminator "\r\n"u8.CopyTo(span[spanOffset..]); spanOffset = chunkTotalLength; @@ -193,7 +196,7 @@ public void Write(ReadOnlySpan source) [MethodImpl(MethodImplOptions.NoInlining)] private void EnsureMore(int count = 0) { - if ((_buffered + count) > _currentChunk.Length) + if (count > (_currentChunk.Length - _buffered)) { if (_buffered > 0) { From fdd5fcfb93f0d3bb025a82cd2ff574fd728327dd Mon Sep 17 00:00:00 2001 From: Damian Edwards Date: Sun, 12 Mar 2023 20:32:33 -0700 Subject: [PATCH 5/9] Allow for chunk terminator in bounds checks --- .../PlatformBenchmarks/ChunkedBufferWriter.cs | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/src/BenchmarksApps/TechEmpower/PlatformBenchmarks/ChunkedBufferWriter.cs b/src/BenchmarksApps/TechEmpower/PlatformBenchmarks/ChunkedBufferWriter.cs index 5ed1ea616..a6069df55 100644 --- a/src/BenchmarksApps/TechEmpower/PlatformBenchmarks/ChunkedBufferWriter.cs +++ b/src/BenchmarksApps/TechEmpower/PlatformBenchmarks/ChunkedBufferWriter.cs @@ -11,6 +11,7 @@ internal sealed class ChunkedBufferWriter : IBufferWriter where T { private const int DefaultChunkSizeHint = 2048; private static readonly StandardFormat DefaultHexFormat = GetHexFormat(DefaultChunkSizeHint); + private static readonly byte[] ChunkTerminator = "\r\n"u8.ToArray(); private TWriter _output; private int _chunkSizeHint; @@ -92,6 +93,8 @@ private static StandardFormat GetHexFormat(int maxValue) [MethodImpl(MethodImplOptions.AggressiveInlining)] private void StartNewChunk(int sizeHint, bool isFirst = false) { + ThrowIfEnded(); + // Header is like: // 520\r\n @@ -142,12 +145,12 @@ private void CommitCurrentChunk(bool isFinal = false, int sizeHint = 0) // Total chunk length: content length as HEX string + \r\n + content + \r\n var spanOffset = headerLength + contentLength; - var chunkTotalLength = spanOffset + 2; + var chunkTotalLength = spanOffset + ChunkTerminator.Length; Debug.Assert(span.Length >= chunkTotalLength, "Bad chunk size calculation."); // Write out the chunk terminator - "\r\n"u8.CopyTo(span[spanOffset..]); + ChunkTerminator.AsSpan().CopyTo(span[spanOffset..]); spanOffset = chunkTotalLength; if (!isFinal) @@ -182,7 +185,7 @@ public void Write(ReadOnlySpan source) { ThrowIfEnded(); - if (_currentChunk.Length >= source.Length) + if (_currentChunk.Length >= (source.Length + ChunkTerminator.Length)) { source.CopyTo(_currentChunk.Span); Advance(source.Length); @@ -196,7 +199,7 @@ public void Write(ReadOnlySpan source) [MethodImpl(MethodImplOptions.NoInlining)] private void EnsureMore(int count = 0) { - if (count > (_currentChunk.Length - _buffered)) + if (count > (_currentChunk.Length - _buffered - ChunkTerminator.Length)) { if (_buffered > 0) { @@ -213,12 +216,12 @@ private void WriteMultiBuffer(ReadOnlySpan source) { while (source.Length > 0) { - if (_currentChunk.Length == 0) + if ((_currentChunk.Length - ChunkTerminator.Length) == 0) { EnsureMore(); } - var writable = Math.Min(source.Length, _currentChunk.Length); + var writable = Math.Min(source.Length, _currentChunk.Length - ChunkTerminator.Length); source[..writable].CopyTo(_currentChunk.Span); source = source[writable..]; Advance(writable); From 5a1f3030f03b7f16c291f506538838900eec32d0 Mon Sep 17 00:00:00 2001 From: Damian Edwards Date: Sun, 12 Mar 2023 21:23:29 -0700 Subject: [PATCH 6/9] Ensure ValueTask result is observed --- .../BenchmarkApplication.Fortunes.cs | 12 +----------- 1 file changed, 1 insertion(+), 11 deletions(-) diff --git a/src/BenchmarksApps/TechEmpower/PlatformBenchmarks/BenchmarkApplication.Fortunes.cs b/src/BenchmarksApps/TechEmpower/PlatformBenchmarks/BenchmarkApplication.Fortunes.cs index 1f8941f62..ca05f4967 100644 --- a/src/BenchmarksApps/TechEmpower/PlatformBenchmarks/BenchmarkApplication.Fortunes.cs +++ b/src/BenchmarksApps/TechEmpower/PlatformBenchmarks/BenchmarkApplication.Fortunes.cs @@ -52,6 +52,7 @@ private ValueTask OutputFortunes(PipeWriter pipeWriter, TModel model, Sl if (renderTask.IsCompletedSuccessfully) { + renderTask.GetAwaiter().GetResult(); EndTemplateRendering(chunkedWriter, template); return ValueTask.CompletedTask; } @@ -59,17 +60,6 @@ private ValueTask OutputFortunes(PipeWriter pipeWriter, TModel model, Sl return AwaitTemplateRenderTask(renderTask, chunkedWriter, template); } - private static ValueTask AsValueTask(ValueTask valueTask) - { - if (valueTask.IsCompletedSuccessfully) - { - var _ = valueTask.GetAwaiter().GetResult(); - return default; - } - - return new ValueTask(valueTask.AsTask()); - } - private static async ValueTask AwaitTemplateRenderTask(ValueTask renderTask, ChunkedBufferWriter chunkedWriter, RazorSlice template) { await renderTask; From edac63d62ddf2ad60d65a4fa5d850d3caca42a2c Mon Sep 17 00:00:00 2001 From: Damian Edwards Date: Mon, 13 Mar 2023 12:37:40 -0700 Subject: [PATCH 7/9] Update TFM targeting to net6.0+ only & resolve warnings & info messages --- .../TechEmpower/Minimal/Minimal.csproj | 2 +- .../TechEmpower/Minimal/Program.cs | 5 +- .../BenchmarkApplication.Caching.cs | 6 +-- .../BenchmarkApplication.Fortunes.cs | 7 +-- .../BenchmarkApplication.HttpConnection.cs | 48 ++----------------- .../BenchmarkApplication.Json.cs | 19 ++------ .../BenchmarkApplication.MultipleQueries.cs | 24 ++-------- .../BenchmarkApplication.SingleQuery.cs | 14 ++---- .../BenchmarkApplication.Updates.cs | 16 ++----- .../BenchmarkApplication.cs | 20 ++------ .../BenchmarkConfigurationHelpers.cs | 5 +- .../Data/BatchUpdateString.cs | 4 -- .../PlatformBenchmarks/Data/EfDb.cs | 17 ++----- .../PlatformBenchmarks/Data/RawDb.cs | 19 ++------ .../PlatformBenchmarks/DateHeader.cs | 2 +- .../PlatformBenchmarks.csproj | 7 +-- .../TechEmpower/PlatformBenchmarks/Program.cs | 4 -- 17 files changed, 41 insertions(+), 178 deletions(-) diff --git a/src/BenchmarksApps/TechEmpower/Minimal/Minimal.csproj b/src/BenchmarksApps/TechEmpower/Minimal/Minimal.csproj index 47f139e51..021c363ba 100644 --- a/src/BenchmarksApps/TechEmpower/Minimal/Minimal.csproj +++ b/src/BenchmarksApps/TechEmpower/Minimal/Minimal.csproj @@ -11,7 +11,7 @@ - + diff --git a/src/BenchmarksApps/TechEmpower/Minimal/Program.cs b/src/BenchmarksApps/TechEmpower/Minimal/Program.cs index 73920f036..b73b0efd2 100644 --- a/src/BenchmarksApps/TechEmpower/Minimal/Program.cs +++ b/src/BenchmarksApps/TechEmpower/Minimal/Program.cs @@ -31,12 +31,11 @@ app.MapGet("/db", async (Db db) => await db.LoadSingleQueryRow()); -var createFortunesTemplate = RazorSlice.ResolveSliceFactory("/Templates/Fortunes.cshtml"); +var createFortunesTemplate = RazorSlice.ResolveSliceFactory>("/Templates/Fortunes.cshtml"); var htmlEncoder = CreateHtmlEncoder(); app.MapGet("/fortunes", async (HttpContext context, Db db) => { var fortunes = await db.LoadFortunesRows(); - var template = (RazorSliceHttpResult>)createFortunesTemplate(); - template.Model = fortunes; + var template = (RazorSliceHttpResult>)createFortunesTemplate(fortunes); template.HtmlEncoder = htmlEncoder; return template; }); diff --git a/src/BenchmarksApps/TechEmpower/PlatformBenchmarks/BenchmarkApplication.Caching.cs b/src/BenchmarksApps/TechEmpower/PlatformBenchmarks/BenchmarkApplication.Caching.cs index 555b79cc6..24896e59b 100644 --- a/src/BenchmarksApps/TechEmpower/PlatformBenchmarks/BenchmarkApplication.Caching.cs +++ b/src/BenchmarksApps/TechEmpower/PlatformBenchmarks/BenchmarkApplication.Caching.cs @@ -8,13 +8,9 @@ namespace PlatformBenchmarks { public partial class BenchmarkApplication { - private async Task Caching(PipeWriter pipeWriter, int count) + private static async Task Caching(PipeWriter pipeWriter, int count) { -#if NET6_0_OR_GREATER OutputMultipleQueries(pipeWriter, await RawDb.LoadCachedQueries(count), SerializerContext.CachedWorldArray); -#else - OutputMultipleQueries(pipeWriter, await RawDb.LoadCachedQueries(count)); -#endif } } } diff --git a/src/BenchmarksApps/TechEmpower/PlatformBenchmarks/BenchmarkApplication.Fortunes.cs b/src/BenchmarksApps/TechEmpower/PlatformBenchmarks/BenchmarkApplication.Fortunes.cs index ca05f4967..d3049722b 100644 --- a/src/BenchmarksApps/TechEmpower/PlatformBenchmarks/BenchmarkApplication.Fortunes.cs +++ b/src/BenchmarksApps/TechEmpower/PlatformBenchmarks/BenchmarkApplication.Fortunes.cs @@ -1,10 +1,8 @@ // 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.IO.Pipelines; using System.Runtime.CompilerServices; -using System.Threading; using System.Threading.Tasks; using RazorSlices; @@ -27,7 +25,7 @@ private async Task FortunesEf(PipeWriter pipeWriter) await OutputFortunes(pipeWriter, await EfDb.LoadFortunesRows(), FortunesEfTemplateFactory); } - private ValueTask OutputFortunes(PipeWriter pipeWriter, TModel model, SliceFactory templateFactory) + private ValueTask OutputFortunes(PipeWriter pipeWriter, TModel model, SliceFactory templateFactory) { // Render headers var preamble = """ @@ -43,8 +41,7 @@ private ValueTask OutputFortunes(PipeWriter pipeWriter, TModel model, Sl pipeWriter.Advance(headersLength); // Render body - var template = (RazorSlice)templateFactory(); - template.Model = model; + var template = templateFactory(model); // Kestrel PipeWriter span size is 4K, headers above already written to first span & template output is ~1350 bytes, // so 2K chunk size should result in only a single span and chunk being used. var chunkedWriter = GetChunkedWriter(pipeWriter, chunkSizeHint: 2048); diff --git a/src/BenchmarksApps/TechEmpower/PlatformBenchmarks/BenchmarkApplication.HttpConnection.cs b/src/BenchmarksApps/TechEmpower/PlatformBenchmarks/BenchmarkApplication.HttpConnection.cs index e9627a316..c14589bb2 100644 --- a/src/BenchmarksApps/TechEmpower/PlatformBenchmarks/BenchmarkApplication.HttpConnection.cs +++ b/src/BenchmarksApps/TechEmpower/PlatformBenchmarks/BenchmarkApplication.HttpConnection.cs @@ -112,21 +112,10 @@ private bool ParseHttpRequest(ref SequenceReader reader, bool isCompleted) if (state == State.StartLine) { -#if NETCOREAPP5_0 || NET5_0 || NET6_0_OR_GREATER if (Parser.ParseRequestLine(new ParsingAdapter(this), ref reader)) { state = State.Headers; } -#else - var unconsumedSequence = reader.Sequence.Slice(reader.Position); - if (Parser.ParseRequestLine(new ParsingAdapter(this), unconsumedSequence, out var consumed, out _)) - { - state = State.Headers; - - var parsedLength = unconsumedSequence.Slice(reader.Position, consumed).Length; - reader.Advance(parsedLength); - } -#endif } if (state == State.Headers) @@ -197,21 +186,10 @@ private bool ParseHttpRequest(ref ReadOnlySequence buffer, bool isComplete if (state == State.StartLine) { -#if NETCOREAPP5_0 || NET5_0 || NET6_0_OR_GREATER if (Parser.ParseRequestLine(new ParsingAdapter(this), ref reader)) { state = State.Headers; } -#else - var unconsumedSequence = reader.Sequence.Slice(reader.Position); - if (Parser.ParseRequestLine(new ParsingAdapter(this), unconsumedSequence, out var consumed, out _)) - { - state = State.Headers; - - var parsedLength = unconsumedSequence.Slice(reader.Position, consumed).Length; - reader.Advance(parsedLength); - } -#endif } if (state == State.Headers) @@ -246,8 +224,6 @@ private bool ParseHttpRequest(ref ReadOnlySequence buffer, bool isComplete } #endif -#if NETCOREAPP5_0 || NET5_0 || NET6_0_OR_GREATER - public void OnStaticIndexedHeader(int index) { } @@ -262,14 +238,6 @@ public void OnHeader(ReadOnlySpan name, ReadOnlySpan value) public void OnHeadersComplete(bool endStream) { } -#else - public void OnHeader(Span name, Span value) - { - } - public void OnHeadersComplete() - { - } -#endif private static void ThrowUnexpectedEndOfData() { @@ -284,8 +252,7 @@ private enum State } [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static BufferWriter GetWriter(PipeWriter pipeWriter, int sizeHint) - => new BufferWriter(new WriterAdapter(pipeWriter), sizeHint); + private static BufferWriter GetWriter(PipeWriter pipeWriter, int sizeHint) => new(new(pipeWriter), sizeHint); [MethodImpl(MethodImplOptions.AggressiveInlining)] private static ChunkedBufferWriter GetChunkedWriter(PipeWriter pipeWriter, int chunkSizeHint) @@ -322,9 +289,8 @@ private struct ParsingAdapter : IHttpRequestLineHandler, IHttpHeadersHandler public ParsingAdapter(BenchmarkApplication requestHandler) => RequestHandler = requestHandler; -#if NETCOREAPP5_0 || NET5_0 || NET6_0_OR_GREATER - public void OnStaticIndexedHeader(int index) - => RequestHandler.OnStaticIndexedHeader(index); + public void OnStaticIndexedHeader(int index) + => RequestHandler.OnStaticIndexedHeader(index); public void OnStaticIndexedHeader(int index, ReadOnlySpan value) => RequestHandler.OnStaticIndexedHeader(index, value); @@ -337,14 +303,6 @@ public void OnHeadersComplete(bool endStream) public void OnStartLine(HttpVersionAndMethod versionAndMethod, TargetOffsetPathLength targetPath, Span startLine) => RequestHandler.OnStartLine(versionAndMethod, targetPath, startLine); -#else - public void OnHeader(Span name, Span value) - => RequestHandler.OnHeader(name, value); - public void OnHeadersComplete() - => RequestHandler.OnHeadersComplete(); - public void OnStartLine(HttpMethod method, HttpVersion version, Span target, Span path, Span query, Span customMethod, bool pathEncoded) - => RequestHandler.OnStartLine(method, version, target, path, query, customMethod, pathEncoded); -#endif } } } diff --git a/src/BenchmarksApps/TechEmpower/PlatformBenchmarks/BenchmarkApplication.Json.cs b/src/BenchmarksApps/TechEmpower/PlatformBenchmarks/BenchmarkApplication.Json.cs index 079247a60..102665aa5 100644 --- a/src/BenchmarksApps/TechEmpower/PlatformBenchmarks/BenchmarkApplication.Json.cs +++ b/src/BenchmarksApps/TechEmpower/PlatformBenchmarks/BenchmarkApplication.Json.cs @@ -8,13 +8,10 @@ namespace PlatformBenchmarks { public partial class BenchmarkApplication { +#if !DATABASE private readonly static uint _jsonPayloadSize = (uint)JsonSerializer.SerializeToUtf8Bytes( new JsonMessage { message = "Hello, World!" }, -#if NET6_0_OR_GREATER SerializerContext.JsonMessage -#else - SerializerOptions -#endif ).Length; private readonly static AsciiString _jsonPreamble = @@ -23,6 +20,7 @@ public partial class BenchmarkApplication _headerContentTypeJson + _crlf + _headerContentLength + _jsonPayloadSize.ToString(); + private static void Json(ref BufferWriter writer, IBufferWriter bodyWriter) { writer.Write(_jsonPreamble); @@ -32,19 +30,12 @@ private static void Json(ref BufferWriter writer, IBufferWriter(PipeWriter pipeWriter, TWord[] rows, JsonTypeInfo jsonTypeInfo) -#else - private static void OutputMultipleQueries(PipeWriter pipeWriter, TWord[] rows) -#endif { var writer = GetWriter(pipeWriter, sizeHint: 160 * rows.Length); // in reality it's 152 for one @@ -39,19 +29,11 @@ private static void OutputMultipleQueries(PipeWriter pipeWriter, TWord[] writer.Commit(); - Utf8JsonWriter utf8JsonWriter = t_writer ??= new Utf8JsonWriter(pipeWriter, new JsonWriterOptions { SkipValidation = true }); + var utf8JsonWriter = t_writer ??= new Utf8JsonWriter(pipeWriter, new JsonWriterOptions { SkipValidation = true }); utf8JsonWriter.Reset(pipeWriter); // Body - JsonSerializer.Serialize( - utf8JsonWriter, - rows, -#if NET6_0_OR_GREATER - jsonTypeInfo -#else - SerializerOptions -#endif - ); + JsonSerializer.Serialize(utf8JsonWriter, rows, jsonTypeInfo); // Content-Length lengthWriter.WriteNumeric((uint)utf8JsonWriter.BytesCommitted); diff --git a/src/BenchmarksApps/TechEmpower/PlatformBenchmarks/BenchmarkApplication.SingleQuery.cs b/src/BenchmarksApps/TechEmpower/PlatformBenchmarks/BenchmarkApplication.SingleQuery.cs index 67713453d..5c00d87c8 100644 --- a/src/BenchmarksApps/TechEmpower/PlatformBenchmarks/BenchmarkApplication.SingleQuery.cs +++ b/src/BenchmarksApps/TechEmpower/PlatformBenchmarks/BenchmarkApplication.SingleQuery.cs @@ -9,7 +9,7 @@ namespace PlatformBenchmarks { public partial class BenchmarkApplication { - private async Task SingleQuery(PipeWriter pipeWriter) + private static async Task SingleQuery(PipeWriter pipeWriter) { OutputSingleQuery(pipeWriter, await RawDb.LoadSingleQueryRow()); } @@ -28,19 +28,11 @@ private static void OutputSingleQuery(PipeWriter pipeWriter, World row) writer.Commit(); - Utf8JsonWriter utf8JsonWriter = t_writer ??= new Utf8JsonWriter(pipeWriter, new JsonWriterOptions { SkipValidation = true }); + var utf8JsonWriter = t_writer ??= new Utf8JsonWriter(pipeWriter, new JsonWriterOptions { SkipValidation = true }); utf8JsonWriter.Reset(pipeWriter); // Body - JsonSerializer.Serialize( - utf8JsonWriter, - row, -#if NET6_0_OR_GREATER - SerializerContext.World -#else - SerializerOptions -#endif - ); + JsonSerializer.Serialize(utf8JsonWriter, row, SerializerContext.World); // Content-Length lengthWriter.WriteNumeric((uint)utf8JsonWriter.BytesCommitted); diff --git a/src/BenchmarksApps/TechEmpower/PlatformBenchmarks/BenchmarkApplication.Updates.cs b/src/BenchmarksApps/TechEmpower/PlatformBenchmarks/BenchmarkApplication.Updates.cs index 0c428bfc4..2d5243649 100644 --- a/src/BenchmarksApps/TechEmpower/PlatformBenchmarks/BenchmarkApplication.Updates.cs +++ b/src/BenchmarksApps/TechEmpower/PlatformBenchmarks/BenchmarkApplication.Updates.cs @@ -9,7 +9,7 @@ namespace PlatformBenchmarks { public partial class BenchmarkApplication { - private async Task Updates(PipeWriter pipeWriter, int count) + private static async Task Updates(PipeWriter pipeWriter, int count) { OutputUpdates(pipeWriter, await RawDb.LoadMultipleUpdatesRows(count)); } @@ -25,22 +25,14 @@ private static void OutputUpdates(PipeWriter pipeWriter, World[] rows) // Date header writer.Write(DateHeader.HeaderBytes); - + writer.Commit(); - Utf8JsonWriter utf8JsonWriter = t_writer ??= new Utf8JsonWriter(pipeWriter, new JsonWriterOptions { SkipValidation = true }); + var utf8JsonWriter = t_writer ??= new Utf8JsonWriter(pipeWriter, new JsonWriterOptions { SkipValidation = true }); utf8JsonWriter.Reset(pipeWriter); // Body - JsonSerializer.Serialize( - utf8JsonWriter, - rows, -#if NET6_0_OR_GREATER - SerializerContext.WorldArray -#else - SerializerOptions -#endif - ); + JsonSerializer.Serialize(utf8JsonWriter, rows, SerializerContext.WorldArray); // Content-Length lengthWriter.WriteNumeric((uint)utf8JsonWriter.BytesCommitted); diff --git a/src/BenchmarksApps/TechEmpower/PlatformBenchmarks/BenchmarkApplication.cs b/src/BenchmarksApps/TechEmpower/PlatformBenchmarks/BenchmarkApplication.cs index d84ac9842..e64048461 100644 --- a/src/BenchmarksApps/TechEmpower/PlatformBenchmarks/BenchmarkApplication.cs +++ b/src/BenchmarksApps/TechEmpower/PlatformBenchmarks/BenchmarkApplication.cs @@ -20,7 +20,6 @@ public partial class BenchmarkApplication public static AsciiString ApplicationName => _applicationName; private readonly static AsciiString _crlf = "\r\n"; - private readonly static AsciiString _eoh = "\r\n\r\n"; // End Of Headers private readonly static AsciiString _http11OK = "HTTP/1.1 200 OK\r\n"; private readonly static AsciiString _http11NotFound = "HTTP/1.1 404 Not Found\r\n"; private readonly static AsciiString _headerServer = "Server: K"; @@ -56,14 +55,13 @@ public bool Return(ChunkedBufferWriter writer) } } - private readonly static SliceFactory FortunesTemplateFactory = RazorSlice.ResolveSliceFactory>("/Templates/Fortunes.cshtml"); - private readonly static SliceFactory FortunesDapperTemplateFactory = RazorSlice.ResolveSliceFactory>("/Templates/FortunesDapper.cshtml"); - private readonly static SliceFactory FortunesEfTemplateFactory = RazorSlice.ResolveSliceFactory>("/Templates/FortunesEf.cshtml"); + private readonly static SliceFactory> FortunesTemplateFactory = RazorSlice.ResolveSliceFactory>("/Templates/Fortunes.cshtml"); + private readonly static SliceFactory> FortunesDapperTemplateFactory = RazorSlice.ResolveSliceFactory>("/Templates/FortunesDapper.cshtml"); + private readonly static SliceFactory> FortunesEfTemplateFactory = RazorSlice.ResolveSliceFactory>("/Templates/FortunesEf.cshtml"); [ThreadStatic] private static Utf8JsonWriter t_writer; -#if NET6_0_OR_GREATER private static readonly JsonContext SerializerContext = JsonContext.Default; [JsonSourceGenerationOptions(GenerationMode = JsonSourceGenerationMode.Serialization)] @@ -73,9 +71,6 @@ public bool Return(ChunkedBufferWriter writer) private partial class JsonContext : JsonSerializerContext { } -#else - private static readonly JsonSerializerOptions SerializerOptions = new JsonSerializerOptions(); -#endif public static class Paths { @@ -93,19 +88,12 @@ public static class Paths private RequestType _requestType; private int _queries; -#if NETCOREAPP5_0 || NET5_0 || NET6_0_OR_GREATER public void OnStartLine(HttpVersionAndMethod versionAndMethod, TargetOffsetPathLength targetPath, Span startLine) { _requestType = versionAndMethod.Method == HttpMethod.Get ? GetRequestType(startLine.Slice(targetPath.Offset, targetPath.Length), ref _queries) : RequestType.NotRecognized; } -#else - public void OnStartLine(HttpMethod method, HttpVersion version, Span target, Span path, Span query, Span customMethod, bool pathEncoded) - { - _requestType = method == HttpMethod.Get ? GetRequestType(path, ref _queries) : RequestType.NotRecognized; - } -#endif - private RequestType GetRequestType(ReadOnlySpan path, ref int queries) + private static RequestType GetRequestType(ReadOnlySpan path, ref int queries) { #if !DATABASE if (path.Length == 10 && path.SequenceEqual(Paths.Plaintext)) diff --git a/src/BenchmarksApps/TechEmpower/PlatformBenchmarks/BenchmarkConfigurationHelpers.cs b/src/BenchmarksApps/TechEmpower/PlatformBenchmarks/BenchmarkConfigurationHelpers.cs index 6331affc6..daf40459d 100644 --- a/src/BenchmarksApps/TechEmpower/PlatformBenchmarks/BenchmarkConfigurationHelpers.cs +++ b/src/BenchmarksApps/TechEmpower/PlatformBenchmarks/BenchmarkConfigurationHelpers.cs @@ -6,7 +6,6 @@ using Microsoft.AspNetCore.Hosting; using Microsoft.AspNetCore.Http; using Microsoft.Extensions.Configuration; -using System.IO.Pipelines; namespace PlatformBenchmarks { @@ -23,16 +22,14 @@ public static IWebHostBuilder UseBenchmarksConfiguration(this IWebHostBuilder bu builder.UseSockets(options => { - if (int.TryParse(builder.GetSetting("threadCount"), out int threadCount)) + if (int.TryParse(builder.GetSetting("threadCount"), out var threadCount)) { options.IOQueueCount = threadCount; } -#if NETCOREAPP5_0 || NET5_0 || NET6_0_OR_GREATER options.WaitForDataBeforeAllocatingBuffer = false; Console.WriteLine($"Options: WaitForData={options.WaitForDataBeforeAllocatingBuffer}, IOQueue={options.IOQueueCount}"); -#endif }); return builder; diff --git a/src/BenchmarksApps/TechEmpower/PlatformBenchmarks/Data/BatchUpdateString.cs b/src/BenchmarksApps/TechEmpower/PlatformBenchmarks/Data/BatchUpdateString.cs index 782f56a98..83b26ef8f 100644 --- a/src/BenchmarksApps/TechEmpower/PlatformBenchmarks/Data/BatchUpdateString.cs +++ b/src/BenchmarksApps/TechEmpower/PlatformBenchmarks/Data/BatchUpdateString.cs @@ -25,11 +25,7 @@ private static string CreateBatch(int batchSize) { var sb = StringBuilderCache.Acquire(); -#if NET6_0_OR_GREATER Func paramNameGenerator = i => "$" + i; -#else - Func paramNameGenerator = i => "@p" + i; -#endif sb.AppendLine("UPDATE world SET randomNumber = CASE id"); for (var i = 0; i < batchSize * 2;) diff --git a/src/BenchmarksApps/TechEmpower/PlatformBenchmarks/Data/EfDb.cs b/src/BenchmarksApps/TechEmpower/PlatformBenchmarks/Data/EfDb.cs index 2a0a18f17..86a2c9e13 100644 --- a/src/BenchmarksApps/TechEmpower/PlatformBenchmarks/Data/EfDb.cs +++ b/src/BenchmarksApps/TechEmpower/PlatformBenchmarks/Data/EfDb.cs @@ -3,11 +3,8 @@ using System.Threading.Tasks; using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore.Infrastructure; -using Microsoft.EntityFrameworkCore.Internal; using Microsoft.EntityFrameworkCore.Storage; -#pragma warning disable EF1001 // Using internal EF pooling APIs, can be cleaned up after 6.0.0-preview4 - namespace PlatformBenchmarks { public class EfDb @@ -19,15 +16,8 @@ public EfDb(AppSettings appSettings) var optionsBuilder = new DbContextOptionsBuilder(); optionsBuilder - .UseNpgsql(appSettings.ConnectionString -#if NET5_0_OR_GREATER - , o => o.ExecutionStrategy(d => new NonRetryingExecutionStrategy(d)) -#endif - ) -#if NET6_0_OR_GREATER - .EnableThreadSafetyChecks(false) -#endif - ; + .UseNpgsql(appSettings.ConnectionString, o => o.ExecutionStrategy(d => new NonRetryingExecutionStrategy(d))) + .EnableThreadSafetyChecks(false); var extension = (optionsBuilder.Options.FindExtension() ?? new CoreOptionsExtension()) .WithMaxPoolSize(1024); @@ -35,8 +25,7 @@ public EfDb(AppSettings appSettings) ((IDbContextOptionsBuilderInfrastructure)optionsBuilder).AddOrUpdateExtension(extension); var options = optionsBuilder.Options; - _dbContextFactory = new PooledDbContextFactory( - new DbContextPool(options)); + _dbContextFactory = new PooledDbContextFactory(options); } public async Task> LoadFortunesRows() diff --git a/src/BenchmarksApps/TechEmpower/PlatformBenchmarks/Data/RawDb.cs b/src/BenchmarksApps/TechEmpower/PlatformBenchmarks/Data/RawDb.cs index d35f44dab..9f83617dc 100644 --- a/src/BenchmarksApps/TechEmpower/PlatformBenchmarks/Data/RawDb.cs +++ b/src/BenchmarksApps/TechEmpower/PlatformBenchmarks/Data/RawDb.cs @@ -56,7 +56,7 @@ public Task LoadCachedQueries(int count) { var id = random.Next(1, 10001); var key = cacheKeys[id]; - if (cache.TryGetValue(key, out object cached)) + if (cache.TryGetValue(key, out var cached)) { result[i] = (CachedWorld)cached; } @@ -75,7 +75,7 @@ static async Task LoadUncachedQueries(int id, int i, int count, R var (cmd, idParameter) = rawdb.CreateReadCommand(db); using var command = cmd; - Func> create = async _ => await rawdb.ReadSingleRow(cmd); + async Task create(ICacheEntry _) => await ReadSingleRow(cmd); var cacheKeys = _cacheKeys; var key = cacheKeys[id]; @@ -221,14 +221,8 @@ public async Task LoadMultipleUpdatesRows(int count) { var randomNumber = _random.Next(1, 10001); -#if NET6_0_OR_GREATER updateCmd.Parameters.Add(new NpgsqlParameter { TypedValue = results[i].Id }); updateCmd.Parameters.Add(new NpgsqlParameter { TypedValue = randomNumber }); -#else - var paramIndex = i * 2 + 1; - updateCmd.Parameters.Add(new NpgsqlParameter(parameterName: BatchUpdateString.ParamNames[paramIndex], value: results[i].Id)); - updateCmd.Parameters.Add(new NpgsqlParameter(parameterName: BatchUpdateString.ParamNames[paramIndex + 1], value: randomNumber)); -#endif results[i].RandomNumber = randomNumber; } @@ -271,13 +265,8 @@ public async Task> LoadFortunesRows() private (NpgsqlCommand readCmd, NpgsqlParameter idParameter) CreateReadCommand(NpgsqlConnection connection) { -#if NET6_0_OR_GREATER var cmd = new NpgsqlCommand("SELECT id, randomnumber FROM world WHERE id = $1", connection); var parameter = new NpgsqlParameter { TypedValue = _random.Next(1, 10001) }; -#else - var cmd = new NpgsqlCommand("SELECT id, randomnumber FROM world WHERE id = @Id", connection); - var parameter = new NpgsqlParameter(parameterName: "@Id", value: _random.Next(1, 10001)); -#endif cmd.Parameters.Add(parameter); @@ -285,7 +274,7 @@ public async Task> LoadFortunesRows() } [MethodImpl(MethodImplOptions.AggressiveInlining)] - private async Task ReadSingleRow(NpgsqlCommand cmd) + private static async Task ReadSingleRow(NpgsqlCommand cmd) { using var rdr = await cmd.ExecuteReaderAsync(System.Data.CommandBehavior.SingleRow); await rdr.ReadAsync(); @@ -301,7 +290,7 @@ private NpgsqlConnection CreateConnection() #if NET7_0_OR_GREATER => _dataSource.CreateConnection(); #else - => new NpgsqlConnection(_connectionString); + => new(_connectionString); #endif private static readonly object[] _cacheKeys = Enumerable.Range(0, 10001).Select((i) => new CacheKey(i)).ToArray(); diff --git a/src/BenchmarksApps/TechEmpower/PlatformBenchmarks/DateHeader.cs b/src/BenchmarksApps/TechEmpower/PlatformBenchmarks/DateHeader.cs index ceb1d3e6f..6804f572f 100644 --- a/src/BenchmarksApps/TechEmpower/PlatformBenchmarks/DateHeader.cs +++ b/src/BenchmarksApps/TechEmpower/PlatformBenchmarks/DateHeader.cs @@ -19,7 +19,7 @@ internal static class DateHeader const int suffixLength = 2; // crlf const int suffixIndex = dateTimeRLength + prefixLength; - private static readonly Timer s_timer = new Timer((s) => { + private static readonly Timer s_timer = new((s) => { SetDateValues(DateTimeOffset.UtcNow); }, null, 1000, 1000); diff --git a/src/BenchmarksApps/TechEmpower/PlatformBenchmarks/PlatformBenchmarks.csproj b/src/BenchmarksApps/TechEmpower/PlatformBenchmarks/PlatformBenchmarks.csproj index cb8c5b938..ddda6973d 100644 --- a/src/BenchmarksApps/TechEmpower/PlatformBenchmarks/PlatformBenchmarks.csproj +++ b/src/BenchmarksApps/TechEmpower/PlatformBenchmarks/PlatformBenchmarks.csproj @@ -1,11 +1,12 @@  - net7.0 + net6.0;net7.0;net8.0 Exe true true true + preview 38063504-d08c-495a-89c9-daaad2f60f31 @@ -19,10 +20,10 @@ - + - + diff --git a/src/BenchmarksApps/TechEmpower/PlatformBenchmarks/Program.cs b/src/BenchmarksApps/TechEmpower/PlatformBenchmarks/Program.cs index 1c4d1feb0..a1a6f1a6f 100644 --- a/src/BenchmarksApps/TechEmpower/PlatformBenchmarks/Program.cs +++ b/src/BenchmarksApps/TechEmpower/PlatformBenchmarks/Program.cs @@ -2,8 +2,6 @@ // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System; -using System.Linq; -using System.Net; using System.Runtime.InteropServices; using System.Threading.Tasks; using Microsoft.AspNetCore.Hosting; @@ -97,7 +95,6 @@ public static IWebHost BuildWebHost(string[] args) }) .UseStartup(); -#if NET5_0 || NET6_0_OR_GREATER hostBuilder.UseSockets(options => { options.WaitForDataBeforeAllocatingBuffer = false; @@ -107,7 +104,6 @@ public static IWebHost BuildWebHost(string[] args) options.UnsafePreferInlineScheduling = Environment.GetEnvironmentVariable("DOTNET_SYSTEM_NET_SOCKETS_INLINE_COMPLETIONS") == "1"; } }); -#endif var host = hostBuilder.Build(); From 1f87c2719ef8abc4a6fa990337a6db50afa111d6 Mon Sep 17 00:00:00 2001 From: Damian Edwards Date: Mon, 13 Mar 2023 12:40:59 -0700 Subject: [PATCH 8/9] Use ReadOnlySpan instead of byte[] --- .../TechEmpower/PlatformBenchmarks/ChunkedBufferWriter.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/BenchmarksApps/TechEmpower/PlatformBenchmarks/ChunkedBufferWriter.cs b/src/BenchmarksApps/TechEmpower/PlatformBenchmarks/ChunkedBufferWriter.cs index a6069df55..66c74b964 100644 --- a/src/BenchmarksApps/TechEmpower/PlatformBenchmarks/ChunkedBufferWriter.cs +++ b/src/BenchmarksApps/TechEmpower/PlatformBenchmarks/ChunkedBufferWriter.cs @@ -11,7 +11,7 @@ internal sealed class ChunkedBufferWriter : IBufferWriter where T { private const int DefaultChunkSizeHint = 2048; private static readonly StandardFormat DefaultHexFormat = GetHexFormat(DefaultChunkSizeHint); - private static readonly byte[] ChunkTerminator = "\r\n"u8.ToArray(); + private static ReadOnlySpan ChunkTerminator => "\r\n"u8; private TWriter _output; private int _chunkSizeHint; @@ -150,7 +150,7 @@ private void CommitCurrentChunk(bool isFinal = false, int sizeHint = 0) Debug.Assert(span.Length >= chunkTotalLength, "Bad chunk size calculation."); // Write out the chunk terminator - ChunkTerminator.AsSpan().CopyTo(span[spanOffset..]); + ChunkTerminator.CopyTo(span[spanOffset..]); spanOffset = chunkTotalLength; if (!isFinal) From c09b5546be369338f74475a3249d655482b29241 Mon Sep 17 00:00:00 2001 From: Damian Edwards Date: Mon, 13 Mar 2023 13:31:30 -0700 Subject: [PATCH 9/9] Don't cache Random.Shared --- src/BenchmarksApps/TechEmpower/Minimal/Database/Db.cs | 6 ++---- src/BenchmarksApps/TechEmpower/Mvc/Database/Db.cs | 9 ++++----- 2 files changed, 6 insertions(+), 9 deletions(-) diff --git a/src/BenchmarksApps/TechEmpower/Minimal/Database/Db.cs b/src/BenchmarksApps/TechEmpower/Minimal/Database/Db.cs index 0a395e67a..95b87542a 100644 --- a/src/BenchmarksApps/TechEmpower/Minimal/Database/Db.cs +++ b/src/BenchmarksApps/TechEmpower/Minimal/Database/Db.cs @@ -8,8 +8,6 @@ public class Db { private static readonly Comparison FortuneSortComparison = (a, b) => string.CompareOrdinal(a.Message, b.Message); - private static readonly Random _random = Random.Shared; - private readonly DbProviderFactory _dbProviderFactory; private readonly string _connectionString; @@ -34,7 +32,7 @@ static Task ReadSingleRow(DbConnection db) { return db.QueryFirstOrDefaultAsync( "SELECT id, randomnumber FROM world WHERE id = @Id", - new { Id = _random.Next(1, 10001) }); + new { Id = Random.Shared.Next(1, 10001) }); } public async Task LoadMultipleQueriesRows(int count) @@ -74,7 +72,7 @@ public async Task LoadMultipleUpdatesRows(int count) for (var i = 0; i < count; i++) { - var randomNumber = _random.Next(1, 10001); + var randomNumber = Random.Shared.Next(1, 10001); parameters[$"@Rn_{i}"] = randomNumber; parameters[$"@Id_{i}"] = results[i].Id; diff --git a/src/BenchmarksApps/TechEmpower/Mvc/Database/Db.cs b/src/BenchmarksApps/TechEmpower/Mvc/Database/Db.cs index cde30225c..9ffbb8741 100644 --- a/src/BenchmarksApps/TechEmpower/Mvc/Database/Db.cs +++ b/src/BenchmarksApps/TechEmpower/Mvc/Database/Db.cs @@ -5,7 +5,6 @@ namespace Mvc.Database; public class Db { - private static readonly Random _random = Random.Shared; private readonly ApplicationDbContext _dbContext; public Db(ApplicationDbContext dbContext) @@ -19,7 +18,7 @@ private static readonly Func> _firstWorld public Task LoadSingleQueryRow() { - var id = _random.Next(1, 10001); + var id = Random.Shared.Next(1, 10001); return _firstWorldQuery(_dbContext, id); } @@ -32,7 +31,7 @@ public async Task LoadMultipleQueriesRows(int count) for (var i = 0; i < count; i++) { - var id = _random.Next(1, 10001); + var id = Random.Shared.Next(1, 10001); result[i] = await _firstWorldQuery(_dbContext, id); } @@ -52,10 +51,10 @@ public async Task LoadMultipleUpdatesRows(int count) for (var i = 0; i < count; i++) { - var id = _random.Next(1, 10001); + var id = Random.Shared.Next(1, 10001); var result = await _firstWorldTrackedQuery(_dbContext, id); - _dbContext.Entry(result).Property("RandomNumber").CurrentValue = _random.Next(1, 10001); + _dbContext.Entry(result).Property("RandomNumber").CurrentValue = Random.Shared.Next(1, 10001); results[i] = result; }