diff --git a/src/BenchmarksApps/TechEmpower/BlazorSSR/Components/FortunesParameters.razor b/src/BenchmarksApps/TechEmpower/BlazorSSR/Components/FortunesParameters.razor new file mode 100644 index 000000000..ab2aa39f1 --- /dev/null +++ b/src/BenchmarksApps/TechEmpower/BlazorSSR/Components/FortunesParameters.razor @@ -0,0 +1,19 @@ + + + + Fortunes + + + + + @foreach (var item in Rows) + { + + } +
idmessage
@item.Id@item.Message
+ + +@code { + [Parameter] + public required List Rows { get; set; } +} diff --git a/src/BenchmarksApps/TechEmpower/BlazorSSR/Database/Db.cs b/src/BenchmarksApps/TechEmpower/BlazorSSR/Database/Db.cs index 2d6f48887..0a49d6098 100644 --- a/src/BenchmarksApps/TechEmpower/BlazorSSR/Database/Db.cs +++ b/src/BenchmarksApps/TechEmpower/BlazorSSR/Database/Db.cs @@ -22,7 +22,7 @@ public async Task> LoadFortunesRowsDapper() await using var connection = _dataSource.CreateConnection(); var result = (await connection.QueryAsync($"SELECT id, message FROM fortune")).AsList(); - result.Add(new Fortune { Id = 0, Message = "Additional fortune added at request time." }); + result.Add(new() { Id = 0, Message = "Additional fortune added at request time." }); result.Sort(FortuneSortComparison); return result; @@ -37,11 +37,36 @@ public async Task> LoadFortunesRowsEf(AppDbContext dbContext) result.Add(fortune); } - result.Add(new Fortune { Id = 0, Message = "Additional fortune added at request time." }); + result.Add(new() { Id = 0, Message = "Additional fortune added at request time." }); result.Sort(FortuneSortComparison); return result; } + public Task> LoadFortunesRowsNoDb() + { + // Benchmark requirements explicitly prohibit pre-initializing the list size + var result = new List + { + new() { Id = 1, Message = "fortune: No such file or directory" }, + new() { Id = 2, Message = "A computer scientist is someone who fixes things that aren't broken." }, + new() { Id = 3, Message = "After enough decimal places, nobody gives a damn." }, + new() { Id = 4, Message = "A bad random number generator: 1, 1, 1, 1, 1, 4.33e+67, 1, 1, 1" }, + new() { Id = 5, Message = "A computer program does what you tell it to do, not what you want it to do." }, + new() { Id = 6, Message = "Emacs is a nice operating system, but I prefer UNIX. — Tom Christaensen" }, + new() { Id = 7, Message = "Any program that runs right is obsolete." }, + new() { Id = 8, Message = "A list is only as strong as its weakest link. — Donald Knuth" }, + new() { Id = 9, Message = "Feature: A bug with seniority." }, + new() { Id = 10, Message = "Computers make very fast, very accurate mistakes." }, + new() { Id = 11, Message = "" }, + new() { Id = 12, Message = "フレームワークのベンチマーク" }, + new() { Id = 0, Message = "Additional fortune added at request time." } + }; + + result.Sort(FortuneSortComparison); + + return Task.FromResult(result); + } + ValueTask IAsyncDisposable.DisposeAsync() => _dataSource.DisposeAsync(); } diff --git a/src/BenchmarksApps/TechEmpower/BlazorSSR/FortunesRazorParameters.cs b/src/BenchmarksApps/TechEmpower/BlazorSSR/FortunesRazorParameters.cs new file mode 100644 index 000000000..21adb0dd1 --- /dev/null +++ b/src/BenchmarksApps/TechEmpower/BlazorSSR/FortunesRazorParameters.cs @@ -0,0 +1,63 @@ +using System.Collections; +using System.Diagnostics.CodeAnalysis; +using BlazorSSR.Components; +using BlazorSSR.Models; + +namespace BlazorSSR; + +internal readonly struct FortunesRazorParameters(List model) : IReadOnlyDictionary +{ + private const string ModelKeyName = nameof(FortunesParameters.Rows); + + private readonly KeyValuePair _modelKvp = new(ModelKeyName, model); + + public object? this[string key] => KeyIsModel(key) ? model : null; + + public IEnumerable Keys { get; } = [ModelKeyName]; + + public IEnumerable Values { get; } = [model]; + + public int Count { get; } = 1; + + public bool ContainsKey(string key) => KeyIsModel(key); + + public IEnumerator> GetEnumerator() => new Enumerator(_modelKvp); + + IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); + + public bool TryGetValue(string key, [MaybeNullWhen(false)] out object? value) + { + if (KeyIsModel(key)) + { + value = model; + return true; + } + value = default; + return false; + } + + private static bool KeyIsModel(string key) => ModelKeyName.Equals(key, StringComparison.Ordinal); + + private struct Enumerator(KeyValuePair kvp) : IEnumerator> + { + private bool _moved; + + public readonly KeyValuePair Current { get; } = kvp; + + readonly object IEnumerator.Current => Current; + + public bool MoveNext() + { + if (_moved) + { + return false; + } + _moved = true; + return true; + } + + public readonly void Dispose() { } + + public void Reset() => throw new NotSupportedException(); + } +} diff --git a/src/BenchmarksApps/TechEmpower/BlazorSSR/Program.cs b/src/BenchmarksApps/TechEmpower/BlazorSSR/Program.cs index 5c65cd6e7..06861d0ee 100644 --- a/src/BenchmarksApps/TechEmpower/BlazorSSR/Program.cs +++ b/src/BenchmarksApps/TechEmpower/BlazorSSR/Program.cs @@ -25,7 +25,6 @@ builder.Services.AddRazorComponents(); builder.Services.AddSingleton(serviceProvider => { - // TODO: This custom configured HtmlEncoder won't actually be used until Blazor supports it: https://github.com/dotnet/aspnetcore/issues/47477 var settings = new TextEncoderSettings(UnicodeRanges.BasicLatin, UnicodeRanges.Katakana, UnicodeRanges.Hiragana); settings.AllowCharacter('\u2014'); // allow EM DASH through return HtmlEncoder.Create(settings); @@ -40,6 +39,18 @@ app.MapGet("/direct/fortunes", () => new RazorComponentResult()); app.MapGet("/direct/fortunes-ef", () => new RazorComponentResult()); +app.MapGet("/direct/fortunes/params", async (HttpContext context, Db db) => { + var fortunes = await db.LoadFortunesRowsDapper(); + //var fortunes = await db.LoadFortunesRowsNoDb(); // Don't call the database + var parameters = new Dictionary { { nameof(FortunesParameters.Rows), fortunes } }; + //var parameters = new FortunesRazorParameters(fortunes); // Custom parameters class to avoid allocating a Dictionary + var result = new RazorComponentResult(parameters) + { + PreventStreamingRendering = true + }; + return result; +}); + app.Lifetime.ApplicationStarted.Register(() => Console.WriteLine("Application started. Press Ctrl+C to shut down.")); app.Lifetime.ApplicationStopping.Register(() => Console.WriteLine("Application is shutting down...")); diff --git a/src/BenchmarksApps/TechEmpower/BlazorSSR/blazorssr.benchmarks.yml b/src/BenchmarksApps/TechEmpower/BlazorSSR/blazorssr.benchmarks.yml index 4950cc078..8189c81ac 100644 --- a/src/BenchmarksApps/TechEmpower/BlazorSSR/blazorssr.benchmarks.yml +++ b/src/BenchmarksApps/TechEmpower/BlazorSSR/blazorssr.benchmarks.yml @@ -51,6 +51,39 @@ scenarios: presetHeaders: html path: /fortunes-ef + fortunes-direct: + db: + job: postgresql + application: + job: blazorssr + load: + job: wrk + variables: + presetHeaders: html + path: /direct/fortunes + + fortunes-direct-ef: + db: + job: postgresql + application: + job: blazorssr + load: + job: wrk + variables: + presetHeaders: html + path: /direct/fortunes-ef + + fortunes-direct-params: + db: + job: postgresql + application: + job: blazorssr + load: + job: wrk + variables: + presetHeaders: html + path: /direct/fortunes/params + profiles: # this profile uses the local folder as the source # instead of the public repository diff --git a/src/BenchmarksApps/TechEmpower/Minimal/Database/Db.cs b/src/BenchmarksApps/TechEmpower/Minimal/Database/Db.cs index 56e161f53..84bb79e79 100644 --- a/src/BenchmarksApps/TechEmpower/Minimal/Database/Db.cs +++ b/src/BenchmarksApps/TechEmpower/Minimal/Database/Db.cs @@ -1,4 +1,4 @@ -using System.Data.Common; +using System.Data.Common; using Dapper; using Minimal.Models; @@ -99,4 +99,29 @@ public async Task> LoadFortunesRows() return result; } + + public Task> LoadFortunesRowsNoDb() + { + // Benchmark requirements explicitly prohibit pre-initializing the list size + var result = new List + { + new(1, "fortune: No such file or directory"), + new(2, "A computer scientist is someone who fixes things that aren't broken."), + new(3, "After enough decimal places, nobody gives a damn."), + new(4, "A bad random number generator: 1, 1, 1, 1, 1, 4.33e+67, 1, 1, 1"), + new(5, "A computer program does what you tell it to do, not what you want it to do."), + new(6, "Emacs is a nice operating system, but I prefer UNIX. — Tom Christaensen"), + new(7, "Any program that runs right is obsolete."), + new(8, "A list is only as strong as its weakest link. — Donald Knuth"), + new(9, "Feature: A bug with seniority."), + new(10, "Computers make very fast, very accurate mistakes."), + new(11, ""), + new(12, "フレームワークのベンチマーク"), + new(0, "Additional fortune added at request time.") + }; + + result.Sort(FortuneSortComparison); + + return Task.FromResult(result); + } } \ No newline at end of file diff --git a/src/BenchmarksApps/TechEmpower/Minimal/Program.cs b/src/BenchmarksApps/TechEmpower/Minimal/Program.cs index 9b0d160e4..9fce631a8 100644 --- a/src/BenchmarksApps/TechEmpower/Minimal/Program.cs +++ b/src/BenchmarksApps/TechEmpower/Minimal/Program.cs @@ -1,7 +1,6 @@ using System.Text.Encodings.Web; using System.Text.Unicode; using Microsoft.AspNetCore.Http.HttpResults; -using RazorSlices; using Minimal; using Minimal.Database; using Minimal.Models; @@ -23,6 +22,7 @@ // Add services to the container. builder.Services.AddSingleton(new Db(appSettings)); +builder.Services.AddSingleton(CreateHtmlEncoder()); var app = builder.Build(); @@ -38,10 +38,9 @@ app.MapGet("/db/result", async (Db db) => Results.Json(await db.LoadSingleQueryRow())); -var htmlEncoder = CreateHtmlEncoder(); - -app.MapGet("/fortunes", async (HttpContext context, Db db) => { +app.MapGet("/fortunes", async (HttpContext context, Db db, HtmlEncoder htmlEncoder) => { var fortunes = await db.LoadFortunesRows(); + //var fortunes = await db.LoadFortunesRowsNoDb(); // Don't call the database var template = (RazorSliceHttpResult>)Fortunes.Create(fortunes); template.HtmlEncoder = htmlEncoder; return template; @@ -65,4 +64,4 @@ 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 +}