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..95b87542a 100644 --- a/src/BenchmarksApps/TechEmpower/Minimal/Database/Db.cs +++ b/src/BenchmarksApps/TechEmpower/Minimal/Database/Db.cs @@ -2,118 +2,101 @@ 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 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.Shared.Next(1, 10001) }); + } - public Task LoadSingleQueryRow() - { - using var db = _dbProviderFactory.CreateConnection(); - db!.ConnectionString = _connectionString; + public async Task LoadMultipleQueriesRows(int count) + { + count = Math.Clamp(count, 1, 500); - // Note: Don't need to open connection if only doing one thing; let dapper do it - return ReadSingleRow(db); - } + var results = new World[count]; + using var db = _dbProviderFactory.CreateConnection(); + + db!.ConnectionString = _connectionString; + await db.OpenAsync(); - static Task ReadSingleRow(DbConnection db) + 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) + { + count = Math.Clamp(count, 1, 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++) { - 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; + results[i] = await ReadSingleRow(db); } - public async Task LoadMultipleUpdatesRows(int 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; + var randomNumber = Random.Shared.Next(1, 10001); + parameters[$"@Rn_{i}"] = randomNumber; + parameters[$"@Id_{i}"] = results[i].Id; + + results[i].RandomNumber = randomNumber; } - public async Task> LoadFortunesRows() - { - List result; + await db.ExecuteAsync(BatchUpdateString.Query(count), parameters); + return results; + } - using var db = _dbProviderFactory.CreateConnection(); + public async Task> LoadFortunesRows() + { + List result; - db!.ConnectionString = _connectionString; + using var db = _dbProviderFactory.CreateConnection(); - // 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(); + db!.ConnectionString = _connectionString; - result.Add(new Fortune(0, "Additional fortune added at request time.")); - result.Sort(FortuneSortComparison); + // 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(); - return result; - } + result.Add(new Fortune(0, "Additional fortune added at request time.")); + result.Sort(FortuneSortComparison); + + 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..021c363ba 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..b73b0efd2 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,13 @@ 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(fortunes); + template.HtmlEncoder = htmlEncoder; + return template; }); app.MapGet("/queries/{count}", async (Db db, int count) => await db.LoadMultipleQueriesRows(count)); @@ -40,3 +48,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/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; } 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 e1901717c..d3049722b 100644 --- a/src/BenchmarksApps/TechEmpower/PlatformBenchmarks/BenchmarkApplication.Fortunes.cs +++ b/src/BenchmarksApps/TechEmpower/PlatformBenchmarks/BenchmarkApplication.Fortunes.cs @@ -1,125 +1,74 @@ // 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.Collections.Generic; -using System.Diagnostics; using System.IO.Pipelines; -using System.Text.Encodings.Web; +using System.Runtime.CompilerServices; 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 = 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); + var renderTask = template.RenderAsync(chunkedWriter, null, 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); + renderTask.GetAwaiter().GetResult(); + 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) + 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)); - - writer.Commit(); + await renderTask; + EndTemplateRendering(chunkedWriter, template); } - private void OutputFortunes(PipeWriter pipeWriter, List model) + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static void EndTemplateRendering(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)); - - writer.Commit(); + 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..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,18 @@ 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) + { + var writer = ChunkedWriterPool.Get(); + writer.SetOutput(new WriterAdapter(pipeWriter), chunkSizeHint); + return writer; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static void ReturnChunkedWriter(ChunkedBufferWriter writer) => ChunkedWriterPool.Return(writer); private struct WriterAdapter : IBufferWriter { @@ -311,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); @@ -326,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 dafeb29b7..e64048461 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 { @@ -17,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"; @@ -25,7 +27,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,22 +35,33 @@ 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; -#if NET6_0_OR_GREATER private static readonly JsonContext SerializerContext = JsonContext.Default; [JsonSourceGenerationOptions(GenerationMode = JsonSourceGenerationMode.Serialization)] @@ -59,9 +71,6 @@ public partial class BenchmarkApplication private partial class JsonContext : JsonSerializerContext { } -#else - private static readonly JsonSerializerOptions SerializerOptions = new JsonSerializerOptions(); -#endif public static class Paths { @@ -79,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)) @@ -157,13 +159,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/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/ChunkedBufferWriter.cs b/src/BenchmarksApps/TechEmpower/PlatformBenchmarks/ChunkedBufferWriter.cs new file mode 100644 index 000000000..66c74b964 --- /dev/null +++ b/src/BenchmarksApps/TechEmpower/PlatformBenchmarks/ChunkedBufferWriter.cs @@ -0,0 +1,238 @@ +using System; +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 = 2048; + private static readonly StandardFormat DefaultHexFormat = GetHexFormat(DefaultChunkSizeHint); + private static ReadOnlySpan ChunkTerminator => "\r\n"u8; + + 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); + + _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 : (BitOperations.Log2((uint)n) >> 2) + 1; + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private void StartNewChunk(int sizeHint, bool isFirst = false) + { + ThrowIfEnded(); + + // 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) || (isFirst && DefaultChunkSizeHint != _chunkSizeHint)) + { + // 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 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(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 + ChunkTerminator.Length; + + Debug.Assert(span.Length >= chunkTotalLength, "Bad chunk size calculation."); + + // Write out the chunk terminator + ChunkTerminator.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; + } + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void Write(ReadOnlySpan source) + { + ThrowIfEnded(); + + if (_currentChunk.Length >= (source.Length + ChunkTerminator.Length)) + { + source.CopyTo(_currentChunk.Span); + Advance(source.Length); + } + else + { + WriteMultiBuffer(source); + } + } + + [MethodImpl(MethodImplOptions.NoInlining)] + private void EnsureMore(int count = 0) + { + if (count > (_currentChunk.Length - _buffered - ChunkTerminator.Length)) + { + if (_buffered > 0) + { + CommitCurrentChunk(isFinal: false, count); + } + else + { + StartNewChunk(count); + } + } + } + + private void WriteMultiBuffer(ReadOnlySpan source) + { + while (source.Length > 0) + { + if ((_currentChunk.Length - ChunkTerminator.Length) == 0) + { + EnsureMore(); + } + + var writable = Math.Min(source.Length, _currentChunk.Length - ChunkTerminator.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/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/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..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; } @@ -241,7 +235,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()) { @@ -270,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); @@ -284,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(); @@ -300,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 090ef4615..ddda6973d 100644 --- a/src/BenchmarksApps/TechEmpower/PlatformBenchmarks/PlatformBenchmarks.csproj +++ b/src/BenchmarksApps/TechEmpower/PlatformBenchmarks/PlatformBenchmarks.csproj @@ -1,10 +1,13 @@  - net7.0 + net6.0;net7.0;net8.0 Exe true true + true + preview + 38063504-d08c-495a-89c9-daaad2f60f31 @@ -17,27 +20,10 @@ + - - - - - - - - - - - - - - - - - - - + diff --git a/src/BenchmarksApps/TechEmpower/PlatformBenchmarks/Program.cs b/src/BenchmarksApps/TechEmpower/PlatformBenchmarks/Program.cs index 35889e774..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; @@ -40,7 +38,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 +57,9 @@ public static IWebHost BuildWebHost(string[] args) var config = new ConfigurationBuilder() .AddJsonFile("appsettings.json") +#if DEBUG + .AddUserSecrets() +#endif .AddEnvironmentVariables() .AddEnvironmentVariables(prefix: "ASPNETCORE_") .AddCommandLine(args) @@ -87,7 +95,6 @@ public static IWebHost BuildWebHost(string[] args) }) .UseStartup(); -#if NET5_0 || NET6_0_OR_GREATER hostBuilder.UseSockets(options => { options.WaitForDataBeforeAllocatingBuffer = false; @@ -97,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(); 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" }