diff --git a/frameworks/CSharp/genhttp/Benchmarks/Benchmarks.csproj b/frameworks/CSharp/genhttp/Benchmarks/Benchmarks.csproj
index a9e3397bed8..896e4f2f86c 100644
--- a/frameworks/CSharp/genhttp/Benchmarks/Benchmarks.csproj
+++ b/frameworks/CSharp/genhttp/Benchmarks/Benchmarks.csproj
@@ -10,31 +10,70 @@
GenHTTP Benchmarks
Test suite to be executed with TechEmpower FrameworkBenchmarks.
- $(DefineConstants);$(GENHTTP_ENGINE_NAME)
-
+ $(GENHTTP_ENGINE_NAME)
+
true
true
+ false
+
+
+
+
+
+
+
+
+
+
+
-
-
+
+
-
+
+
+
+
+
+
+
+
+
+
+
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
-
+
-
-
+
+
+
diff --git a/frameworks/CSharp/genhttp/Benchmarks/Model/Database.cs b/frameworks/CSharp/genhttp/Benchmarks/Model/Database.cs
new file mode 100644
index 00000000000..e1335a014ed
--- /dev/null
+++ b/frameworks/CSharp/genhttp/Benchmarks/Model/Database.cs
@@ -0,0 +1,17 @@
+namespace Benchmarks.Model;
+
+using Npgsql;
+
+public static class Database
+{
+
+ public static readonly NpgsqlDataSource DataSource = BuildDataSource();
+
+ private static NpgsqlDataSource BuildDataSource()
+ {
+ var connectionString = Environment.GetEnvironmentVariable("DB_CONNECTION");
+
+ return new NpgsqlSlimDataSourceBuilder(connectionString).EnableArrays().Build();
+ }
+
+}
diff --git a/frameworks/CSharp/genhttp/Benchmarks/Model/DatabaseContext.cs b/frameworks/CSharp/genhttp/Benchmarks/Model/DatabaseContext.cs
deleted file mode 100644
index f27f1d8e245..00000000000
--- a/frameworks/CSharp/genhttp/Benchmarks/Model/DatabaseContext.cs
+++ /dev/null
@@ -1,43 +0,0 @@
-using Microsoft.EntityFrameworkCore;
-
-namespace Benchmarks.Model;
-
-public sealed class DatabaseContext : DbContext
-{
- private static DbContextOptions _options;
-
- private static DbContextOptions _noTrackingOptions;
-
- #region Factory
-
- public static DatabaseContext Create() => new(_options ??= GetOptions(true));
-
- public static DatabaseContext CreateNoTracking() => new(_noTrackingOptions ??= GetOptions(false));
-
- private static DbContextOptions GetOptions(bool tracking)
- {
- var optionsBuilder = new DbContextOptionsBuilder();
-
- optionsBuilder.UseNpgsql("Server=tfb-database;Database=hello_world;User Id=benchmarkdbuser;Password=benchmarkdbpass;SSL Mode=Disable;Maximum Pool Size=512;NoResetOnClose=true;Enlist=false;Max Auto Prepare=4");
-
- if (!tracking)
- {
- optionsBuilder.UseQueryTrackingBehavior(QueryTrackingBehavior.NoTracking);
- }
-
- return optionsBuilder.Options;
- }
-
- private DatabaseContext(DbContextOptions options) : base(options) { }
-
- #endregion
-
- #region Entities
-
- public DbSet World { get; set; }
-
- public DbSet Fortune { get; set; }
-
- #endregion
-
-}
diff --git a/frameworks/CSharp/genhttp/Benchmarks/Model/FortuneMap.cs b/frameworks/CSharp/genhttp/Benchmarks/Model/FortuneMap.cs
new file mode 100644
index 00000000000..cf80593b746
--- /dev/null
+++ b/frameworks/CSharp/genhttp/Benchmarks/Model/FortuneMap.cs
@@ -0,0 +1,80 @@
+using System.Collections;
+using Cottle;
+
+namespace Benchmarks.Model;
+
+public readonly struct FortuneMap : IMap
+{
+ private readonly int _id;
+ private readonly string _message;
+
+ public FortuneMap(int id, string message)
+ {
+ _id = id;
+ _message = message;
+ }
+
+ public Value this[Value key] => TryGet(key, out var value) ? value : Value.Undefined;
+
+ public int Count => 2;
+
+ public bool Contains(Value key)
+ {
+ var keyStr = key.AsString;
+ return keyStr == "id" || keyStr == "message";
+ }
+
+ public bool TryGet(Value key, out Value value)
+ {
+ var keyStr = key.AsString;
+ if (keyStr == "id")
+ {
+ value = _id;
+ return true;
+ }
+ if (keyStr == "message")
+ {
+ value = _message;
+ return true;
+ }
+
+ value = Value.Undefined;
+ return false;
+ }
+
+ public int CompareTo(IMap other)
+ {
+ if (other == null) return 1;
+
+ if (other.TryGet("id", out var otherId))
+ {
+ return _id.CompareTo(otherId.AsNumber);
+ }
+
+ return Count.CompareTo(other.Count);
+ }
+
+ public bool Equals(IMap other)
+ {
+ if (other == null || other.Count != Count)
+ return false;
+
+ return other.TryGet("id", out var otherId) &&
+ other.TryGet("message", out var otherMsg) &&
+ _id == (int)otherId.AsNumber &&
+ _message == otherMsg.AsString;
+ }
+
+ public IEnumerator> GetEnumerator()
+ {
+ yield return new KeyValuePair("id", _id);
+ yield return new KeyValuePair("message", _message);
+ }
+
+ IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
+
+ public override bool Equals(object obj) => obj is IMap map && Equals(map);
+
+ public override int GetHashCode() => HashCode.Combine(_id, _message);
+
+}
\ No newline at end of file
diff --git a/frameworks/CSharp/genhttp/Benchmarks/Program.Internal.cs b/frameworks/CSharp/genhttp/Benchmarks/Program.Internal.cs
new file mode 100644
index 00000000000..3813f5ae438
--- /dev/null
+++ b/frameworks/CSharp/genhttp/Benchmarks/Program.Internal.cs
@@ -0,0 +1,9 @@
+using Benchmarks;
+
+using GenHTTP.Engine.Internal;
+
+var project = Project.Create();
+
+return await Host.Create()
+ .Handler(project)
+ .RunAsync();
diff --git a/frameworks/CSharp/genhttp/Benchmarks/Program.Kestrel.cs b/frameworks/CSharp/genhttp/Benchmarks/Program.Kestrel.cs
new file mode 100644
index 00000000000..41779a8abc8
--- /dev/null
+++ b/frameworks/CSharp/genhttp/Benchmarks/Program.Kestrel.cs
@@ -0,0 +1,9 @@
+using Benchmarks;
+
+using GenHTTP.Engine.Kestrel;
+
+var project = Project.Create();
+
+return await Host.Create()
+ .Handler(project)
+ .RunAsync();
diff --git a/frameworks/CSharp/genhttp/Benchmarks/Program.Unhinged.cs b/frameworks/CSharp/genhttp/Benchmarks/Program.Unhinged.cs
new file mode 100644
index 00000000000..f5d91eee4a5
--- /dev/null
+++ b/frameworks/CSharp/genhttp/Benchmarks/Program.Unhinged.cs
@@ -0,0 +1,17 @@
+using Benchmarks;
+
+using Unhinged;
+using Unhinged.GenHttp.Experimental;
+
+var project = Project.Create();
+
+UnhingedEngine.CreateBuilder()
+ .SetPort(8080)
+ .SetNWorkersSolver(() => Environment.ProcessorCount)
+ .SetBacklog(16384)
+ .SetMaxEventsPerWake(512)
+ .SetMaxNumberConnectionsPerWorker(1024)
+ .SetSlabSizes(32 * 1024, 16 * 1024)
+ .Map(project)
+ .Build()
+ .Run();
diff --git a/frameworks/CSharp/genhttp/Benchmarks/Program.Wired.cs b/frameworks/CSharp/genhttp/Benchmarks/Program.Wired.cs
new file mode 100644
index 00000000000..b46e28cada0
--- /dev/null
+++ b/frameworks/CSharp/genhttp/Benchmarks/Program.Wired.cs
@@ -0,0 +1,14 @@
+using Benchmarks;
+
+using GenHTTP.Adapters.WiredIO;
+
+using Wired.IO.App;
+
+var project = Project.Create();
+
+await WiredApp.CreateExpressBuilder()
+ .Port(8080)
+ .MapGenHttp("*", project)
+ .Build()
+ .RunAsync();
+
\ No newline at end of file
diff --git a/frameworks/CSharp/genhttp/Benchmarks/Program.cs b/frameworks/CSharp/genhttp/Benchmarks/Program.cs
deleted file mode 100644
index 91323662c2d..00000000000
--- a/frameworks/CSharp/genhttp/Benchmarks/Program.cs
+++ /dev/null
@@ -1,26 +0,0 @@
-using Benchmarks.Tests;
-using Benchmarks.Utilities;
-
-#if INTERNAL
-using GenHTTP.Engine.Internal;
-#else
-using GenHTTP.Engine.Kestrel;
-#endif
-
-using GenHTTP.Modules.IO;
-using GenHTTP.Modules.Layouting;
-using GenHTTP.Modules.Webservices;
-
-var tests = Layout.Create()
- .Add("plaintext", Content.From(Resource.FromString("Hello, World!")))
- .Add("json", new JsonHandler())
- .Add("fortunes", new FortuneHandler())
- .AddService("db")
- .AddService("queries")
- .AddService("updates")
- .AddService("cached-worlds")
- .Add(ServerHeader.Create());
-
-return await Host.Create()
- .Handler(tests)
- .RunAsync();
diff --git a/frameworks/CSharp/genhttp/Benchmarks/Project.cs b/frameworks/CSharp/genhttp/Benchmarks/Project.cs
new file mode 100644
index 00000000000..13bae801b07
--- /dev/null
+++ b/frameworks/CSharp/genhttp/Benchmarks/Project.cs
@@ -0,0 +1,31 @@
+using Benchmarks.Tests;
+using Benchmarks.Utilities;
+
+using GenHTTP.Api.Content;
+
+using GenHTTP.Modules.IO;
+using GenHTTP.Modules.Layouting;
+using GenHTTP.Modules.Reflection;
+using GenHTTP.Modules.Webservices;
+
+namespace Benchmarks;
+
+public static class Project
+{
+
+ public static IHandlerBuilder Create()
+ {
+ var mode = ExecutionMode.Auto;
+
+ return Layout.Create()
+ .Add("plaintext", Content.From(Resource.FromString("Hello, World!")))
+ .Add("json", new JsonHandler())
+ .Add("fortunes", new FortuneHandler())
+ .AddService("db", mode: mode)
+ .AddService("queries", mode: mode)
+ .AddService("updates", mode: mode)
+ .AddService("cached-worlds", mode: mode)
+ .Add(ServerHeader.Create());
+ }
+
+}
diff --git a/frameworks/CSharp/genhttp/Benchmarks/Tests/CacheResource.cs b/frameworks/CSharp/genhttp/Benchmarks/Tests/CacheResource.cs
index e0e4bc50d91..cb7a5a96215 100644
--- a/frameworks/CSharp/genhttp/Benchmarks/Tests/CacheResource.cs
+++ b/frameworks/CSharp/genhttp/Benchmarks/Tests/CacheResource.cs
@@ -1,20 +1,26 @@
-using Benchmarks.Model;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Threading.Tasks;
+
+using Benchmarks.Model;
+
using GenHTTP.Modules.Webservices;
-using Microsoft.EntityFrameworkCore;
+
using Microsoft.Extensions.Caching.Memory;
+using Npgsql;
+
namespace Benchmarks.Tests;
public sealed class CacheResource
{
- private static readonly Random Random = new();
-
private static readonly MemoryCache Cache = new(new MemoryCacheOptions
{
ExpirationScanFrequency = TimeSpan.FromMinutes(60)
});
- private static readonly object[] CacheKeys = Enumerable.Range(0, 10001).Select(i => new CacheKey(i)).ToArray();
+ private static readonly CacheKey[] CacheKeys = Enumerable.Range(0, 10001).Select(i => new CacheKey(i)).ToArray();
[ResourceMethod(":queries")]
public ValueTask> GetWorldsFromPath(string queries) => GetWorlds(queries);
@@ -22,9 +28,10 @@ public sealed class CacheResource
[ResourceMethod]
public async ValueTask> GetWorlds(string queries)
{
- var count = 1;
-
- int.TryParse(queries, out count);
+ if (!int.TryParse(queries, out var count))
+ {
+ count = 1;
+ }
if (count < 1)
{
@@ -35,13 +42,13 @@ public async ValueTask> GetWorlds(string queries)
count = 500;
}
- var result = new List(count);
+ NpgsqlConnection connection = null;
- await using var context = DatabaseContext.CreateNoTracking();
+ var result = new List(count);
for (var i = 0; i < count; i++)
{
- var id = Random.Next(1, 10001);
+ var id = Random.Shared.Next(1, 10001);
var key = CacheKeys[id];
@@ -53,7 +60,12 @@ public async ValueTask> GetWorlds(string queries)
}
else
{
- var resolved = await context.World.FirstOrDefaultAsync(w => w.Id == id).ConfigureAwait(false);
+ if (connection == null)
+ {
+ connection = await Database.DataSource.OpenConnectionAsync();
+ }
+
+ var resolved = await GetWorldById(connection, id);
Cache.Set(key, resolved);
@@ -64,6 +76,28 @@ public async ValueTask> GetWorlds(string queries)
return result;
}
+ private static async Task GetWorldById(NpgsqlConnection connection, int id)
+ {
+ await using var command = connection.CreateCommand();
+
+ command.CommandText = "SELECT id, randomnumber FROM world WHERE id = @Id";
+
+ command.Parameters.AddWithValue("@Id", id);
+
+ await using var reader = await command.ExecuteReaderAsync();
+
+ if (await reader.ReadAsync())
+ {
+ return new()
+ {
+ Id = reader.GetInt32(0),
+ RandomNumber = reader.GetInt32(1)
+ };
+ }
+
+ return null;
+ }
+
public sealed class CacheKey : IEquatable
{
private readonly int _value;
@@ -81,4 +115,5 @@ public CacheKey(int value)
public override string ToString() => _value.ToString();
}
+
}
diff --git a/frameworks/CSharp/genhttp/Benchmarks/Tests/DbResource.cs b/frameworks/CSharp/genhttp/Benchmarks/Tests/DbResource.cs
index ac33808fdfd..e7363d438b4 100644
--- a/frameworks/CSharp/genhttp/Benchmarks/Tests/DbResource.cs
+++ b/frameworks/CSharp/genhttp/Benchmarks/Tests/DbResource.cs
@@ -1,21 +1,36 @@
using Benchmarks.Model;
using GenHTTP.Modules.Webservices;
-using Microsoft.EntityFrameworkCore;
namespace Benchmarks.Tests;
public sealed class DbResource
{
- private static readonly Random Random = new();
[ResourceMethod]
- public async ValueTask GetRandomWorld()
+ public Task GetRandomWorld() => GetWorldById(Random.Shared.Next(1, 10001));
+
+ private static async Task GetWorldById(int id)
{
- var id = Random.Next(1, 10001);
+ await using var connection = await Database.DataSource.OpenConnectionAsync();
+
+ await using var command = connection.CreateCommand();
+
+ command.CommandText = "SELECT id, randomnumber FROM world WHERE id = @Id";
+
+ command.Parameters.AddWithValue("@Id", id);
+
+ await using var reader = await command.ExecuteReaderAsync();
- await using var context = DatabaseContext.CreateNoTracking();
+ if (await reader.ReadAsync())
+ {
+ return new()
+ {
+ Id = reader.GetInt32(0),
+ RandomNumber = reader.GetInt32(1)
+ };
+ }
- return await context.World.FirstOrDefaultAsync(w => w.Id == id).ConfigureAwait(false);
+ return null;
}
}
diff --git a/frameworks/CSharp/genhttp/Benchmarks/Tests/FortuneHandler.cs b/frameworks/CSharp/genhttp/Benchmarks/Tests/FortuneHandler.cs
index c7a488f5e78..b3b3fab47f4 100644
--- a/frameworks/CSharp/genhttp/Benchmarks/Tests/FortuneHandler.cs
+++ b/frameworks/CSharp/genhttp/Benchmarks/Tests/FortuneHandler.cs
@@ -1,85 +1,106 @@
-using System.Web;
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Linq;
+using System.Threading.Tasks;
+using System.Web;
using Benchmarks.Model;
+
using Cottle;
+
using GenHTTP.Api.Content;
using GenHTTP.Api.Protocol;
using GenHTTP.Modules.IO;
using GenHTTP.Modules.Pages;
-using GenHTTP.Modules.Pages.Rendering;
-
-using Microsoft.EntityFrameworkCore;
namespace Benchmarks.Tests;
public class FortuneHandler : IHandler
{
+ private IDocument _template;
- #region Get-/Setters
-
- private TemplateRenderer Template { get; }
-
- #endregion
-
- #region Initialization
+ #region Functionality
- public FortuneHandler()
+ public async ValueTask PrepareAsync()
{
var resource = Resource.FromAssembly("Template.html").Build();
- Template = Renderer.From(resource);
+ using var reader = new StreamReader(await resource.GetContentAsync());
+
+ _template = Document.CreateDefault(reader).DocumentOrThrow;
}
- #endregion
-
- #region Functionality
-
- public ValueTask PrepareAsync() => new();
-
public async ValueTask HandleAsync(IRequest request)
{
- var data = new Dictionary
+ if (_template == null)
{
- ["cookies"] = Value.FromEnumerable(await GetFortunes())
- };
+ await PrepareAsync();
+ }
+
+ var template = _template ?? throw new InvalidOperationException("Template has not been initialized");
+
+ var fortunes = await GetFortunes();
+
+ var context = BuildContext(fortunes);
- return request.GetPage(await Template.RenderAsync(data)).Build();
+ var content = template.Render(context);
+
+ return request.GetPage(content).Build();
}
- private static async ValueTask> GetFortunes()
+ private static async Task> GetFortunes()
{
- await using var context = DatabaseContext.CreateNoTracking();
+ var fortunes = await QueryDatabaseAsync();
+
+ fortunes.Add(new() { Id = 0, Message = "Additional fortune added at request time." });
+
+ fortunes.Sort((x, y) => string.CompareOrdinal(x.Message, y.Message));
+
+ return fortunes;
+ }
- var fortunes = await context.Fortune.ToListAsync().ConfigureAwait(false);
+ private static async Task> QueryDatabaseAsync()
+ {
+ await using var connection = await Database.DataSource.OpenConnectionAsync();
- var result = new List(fortunes.Count + 1);
+ await using var command = connection.CreateCommand();
- foreach (var fortune in fortunes)
+ command.CommandText = "SELECT id, message FROM fortune";
+
+ var result = new List(16);
+
+ await using var reader = await command.ExecuteReaderAsync();
+
+ while (await reader.ReadAsync())
{
- result.Add(Value.FromDictionary(new Dictionary()
+ result.Add(new ()
{
- ["id"] = fortune.Id,
- ["message"] = HttpUtility.HtmlEncode(fortune.Message)
- }));
+ Id = reader.GetInt32(0),
+ Message = HttpUtility.HtmlEncode(reader.GetString(1))
+ });
}
- result.Add(Value.FromDictionary(new Dictionary()
- {
- ["id"] = 0,
- ["message"] = "Additional fortune added at request time."
- }));
-
- result.Sort((one, two) =>
- {
- var firstMessage = one.Fields["message"].AsString;
- var secondMessage = two.Fields["message"].AsString;
-
- return string.Compare(firstMessage, secondMessage, StringComparison.Ordinal);
- });
-
return result;
}
+ private static IContext BuildContext(List cookies)
+ {
+ var values = new Value[cookies.Count];
+
+ for (var i = 0; i < cookies.Count; i++)
+ {
+ values[i] = Value.FromMap(new FortuneMap(cookies[i].Id, cookies[i].Message));
+ }
+
+ var store = new Dictionary
+ {
+ ["cookies"] = Value.FromEnumerable(values.ToArray())
+ };
+
+ return Context.CreateBuiltin(store);
+ }
+
#endregion
-
+
}
diff --git a/frameworks/CSharp/genhttp/Benchmarks/Tests/QueryResource.cs b/frameworks/CSharp/genhttp/Benchmarks/Tests/QueryResource.cs
index 592d9083242..3bf10aef302 100644
--- a/frameworks/CSharp/genhttp/Benchmarks/Tests/QueryResource.cs
+++ b/frameworks/CSharp/genhttp/Benchmarks/Tests/QueryResource.cs
@@ -1,22 +1,28 @@
-using Benchmarks.Model;
+using System;
+using System.Collections.Generic;
+using System.Threading.Tasks;
+
+using Benchmarks.Model;
+
using GenHTTP.Modules.Webservices;
-using Microsoft.EntityFrameworkCore;
+
+using NpgsqlTypes;
namespace Benchmarks.Tests;
public sealed class QueryResource
{
- private static readonly Random Random = new();
[ResourceMethod(":queries")]
- public ValueTask> GetWorldsFromPath(string queries) => GetWorlds(queries);
+ public Task> GetWorldsFromPath(string queries) => GetWorlds(queries);
[ResourceMethod]
- public async ValueTask> GetWorlds(string queries)
+ public Task> GetWorlds(string queries)
{
- var count = 1;
-
- int.TryParse(queries, out count);
+ if (!int.TryParse(queries, out var count))
+ {
+ count = 1;
+ }
if (count < 1)
{
@@ -27,18 +33,38 @@ public async ValueTask> GetWorlds(string queries)
count = 500;
}
+ return GetRandomWorlds(count);
+ }
+
+ private static async Task> GetRandomWorlds(int count)
+ {
var result = new List(count);
- using var context = DatabaseContext.CreateNoTracking();
+ await using var connection = await Database.DataSource.OpenConnectionAsync();
+
+ await using var command = connection.CreateCommand();
+
+ command.CommandText = "SELECT id, randomnumber FROM world WHERE id = @Id";
- for (var _ = 0; _ < count; _++)
+ var parameter = command.Parameters.Add("@Id", NpgsqlDbType.Integer);
+
+ for (var i = 0; i < count; i++)
{
- var id = Random.Next(1, 10001);
+ parameter.Value = Random.Shared.Next(1, 10001);
- result.Add(await context.World.FirstOrDefaultAsync(w => w.Id == id).ConfigureAwait(false));
- }
+ await using var reader = await command.ExecuteReaderAsync();
+ if (await reader.ReadAsync())
+ {
+ result.Add(new()
+ {
+ Id = reader.GetInt32(0),
+ RandomNumber = reader.GetInt32(1)
+ });
+ }
+ }
+
return result;
}
-}
\ No newline at end of file
+}
diff --git a/frameworks/CSharp/genhttp/Benchmarks/Tests/UpdateResource.cs b/frameworks/CSharp/genhttp/Benchmarks/Tests/UpdateResource.cs
index 9b2ce8e270c..deb58893776 100644
--- a/frameworks/CSharp/genhttp/Benchmarks/Tests/UpdateResource.cs
+++ b/frameworks/CSharp/genhttp/Benchmarks/Tests/UpdateResource.cs
@@ -1,22 +1,28 @@
-using Benchmarks.Model;
+using System;
+using System.Runtime.CompilerServices;
+using System.Threading.Tasks;
+using Benchmarks.Model;
+
using GenHTTP.Modules.Webservices;
-using Microsoft.EntityFrameworkCore;
+
+using Npgsql;
+using NpgsqlTypes;
namespace Benchmarks.Tests;
public sealed class UpdateResource
{
- private static readonly Random Random = new();
[ResourceMethod(":queries")]
- public ValueTask> UpdateWorldsFromPath(string queries) => UpdateWorlds(queries);
+ public ValueTask UpdateWorldsFromPath(string queries) => UpdateWorlds(queries);
[ResourceMethod]
- public async ValueTask> UpdateWorlds(string queries)
+ public ValueTask UpdateWorlds(string queries)
{
- var count = 1;
-
- int.TryParse(queries, out count);
+ if (!int.TryParse(queries, out var count))
+ {
+ count = 1;
+ }
if (count < 1)
{
@@ -26,39 +32,87 @@ public async ValueTask> UpdateWorlds(string queries)
{
count = 500;
}
+
+ return FetchAndUpdateWorlds(count);
+ }
- var result = new List(count);
+ private async ValueTask FetchAndUpdateWorlds(int count)
+ {
+ var results = new World[count];
- var ids = Enumerable.Range(1, 10000).Select(x => Random.Next(1, 10001)).Distinct().Take(count).ToArray();
+ var ids = new int[count];
+ var numbers = new int[count];
- using (var context = DatabaseContext.Create())
+ for (var i = 0; i < count; i++)
+ {
+ ids[i] = Random.Shared.Next(1, 10001);
+ }
+
+ Array.Sort(ids);
+
+ for (var i = 1; i < count; i++)
+ if (ids[i] == ids[i - 1])
+ ids[i] = (ids[i] % 10000) + 1;
+
+ using var connection = await Database.DataSource.OpenConnectionAsync();
+
+ // Each row must be selected randomly using one query in the same fashion as the single database query test
+ // Use of IN clauses or similar means to consolidate multiple queries into one operation is not permitted.
+ // Similarly, use of a batch or multiple SELECTs within a single statement are not permitted
+ var (queryCmd, queryParameter) = CreateReadCommand(connection);
+ using (queryCmd)
{
- foreach (var id in ids)
+ for (var i = 0; i < results.Length; i++)
{
- var record = await context.World.FirstOrDefaultAsync(w => w.Id == id).ConfigureAwait(false);
+ queryParameter.TypedValue = ids[i];
+ results[i] = await ReadSingleRow(queryCmd);
+ }
+ }
- var old = record.RandomNumber;
+ for (var i = 0; i < count; i++)
+ {
+ var randomNumber = Random.Shared.Next(1, 10001);
+ if (results[i].RandomNumber == randomNumber)
+ {
+ randomNumber = (randomNumber % 10000) + 1;
+ }
- var current = old;
+ results[i].RandomNumber = randomNumber;
+ numbers[i] = randomNumber;
+ }
- for (var i = 0; i < 5; i++)
- {
- current = Random.Next(1, 10001);
+ var update = "UPDATE world w SET randomnumber = u.new_val FROM (SELECT unnest($1) as id, unnest($2) as new_val) u WHERE w.id = u.id";
- if (current != old)
- {
- break;
- }
- }
+ using var updateCmd = new NpgsqlCommand(update, connection);
+ updateCmd.Parameters.Add(new NpgsqlParameter { TypedValue = ids, NpgsqlDbType = NpgsqlDbType.Array | NpgsqlDbType.Integer });
+ updateCmd.Parameters.Add(new NpgsqlParameter { TypedValue = numbers, NpgsqlDbType = NpgsqlDbType.Array | NpgsqlDbType.Integer });
- record.RandomNumber = current;
+ await updateCmd.ExecuteNonQueryAsync();
- result.Add(record);
+ return results;
+ }
+
+ private (NpgsqlCommand readCmd, NpgsqlParameter idParameter) CreateReadCommand(NpgsqlConnection connection)
+ {
+ var cmd = new NpgsqlCommand("SELECT id, randomnumber FROM world WHERE id = $1", connection);
+ var parameter = new NpgsqlParameter { TypedValue = Random.Shared.Next(1, 10001) };
- await context.SaveChangesAsync();
- }
- }
+ cmd.Parameters.Add(parameter);
+
+ return (cmd, parameter);
+ }
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ private static async Task ReadSingleRow(NpgsqlCommand cmd)
+ {
+ using var rdr = await cmd.ExecuteReaderAsync(System.Data.CommandBehavior.SingleRow);
+ await rdr.ReadAsync();
- return result;
+ return new World
+ {
+ Id = rdr.GetInt32(0),
+ RandomNumber = rdr.GetInt32(1)
+ };
}
-}
\ No newline at end of file
+
+}
diff --git a/frameworks/CSharp/genhttp/benchmark_config.json b/frameworks/CSharp/genhttp/benchmark_config.json
index f2376bf64fc..0f024d1b5ba 100644
--- a/frameworks/CSharp/genhttp/benchmark_config.json
+++ b/frameworks/CSharp/genhttp/benchmark_config.json
@@ -45,6 +45,50 @@
"database_os": "Linux",
"display_name": "genhttp [kestrel]",
"notes": ""
+ },
+ "wired": {
+ "plaintext_url": "/plaintext",
+ "json_url": "/json",
+ "db_url": "/db",
+ "query_url": "/queries/",
+ "update_url": "/updates/",
+ "fortune_url": "/fortunes",
+ "cached_query_url": "/cached-worlds/",
+ "port": 8080,
+ "approach": "Realistic",
+ "classification": "Fullstack",
+ "database": "Postgres",
+ "framework": "GenHTTP",
+ "language": "C#",
+ "orm": "Raw",
+ "platform": ".NET",
+ "webserver": "Wired.IO",
+ "os": "Linux",
+ "database_os": "Linux",
+ "display_name": "genhttp [wired.io]",
+ "notes": ""
+ },
+ "unhinged": {
+ "plaintext_url": "/plaintext",
+ "json_url": "/json",
+ "db_url": "/db",
+ "query_url": "/queries/",
+ "update_url": "/updates/",
+ "fortune_url": "/fortunes",
+ "cached_query_url": "/cached-worlds/",
+ "port": 8080,
+ "approach": "Realistic",
+ "classification": "Fullstack",
+ "database": "Postgres",
+ "framework": "GenHTTP",
+ "language": "C#",
+ "orm": "Raw",
+ "platform": ".NET",
+ "webserver": "Unhinged",
+ "os": "Linux",
+ "database_os": "Linux",
+ "display_name": "genhttp [unhinged]",
+ "notes": ""
}
}]
}
diff --git a/frameworks/CSharp/genhttp/config.toml b/frameworks/CSharp/genhttp/config.toml
index 538213c05b4..2338b765a36 100644
--- a/frameworks/CSharp/genhttp/config.toml
+++ b/frameworks/CSharp/genhttp/config.toml
@@ -36,3 +36,39 @@ orm = "Raw"
platform = ".NET"
webserver = "Kestrel"
versus = "None"
+
+[wired]
+urls.plaintext = "/plaintext"
+urls.json = "/json"
+urls.db = "/db"
+urls.query = "/queries/"
+urls.update = "/updates/"
+urls.fortune = "/fortunes"
+urls.cached_query = "/cached-worlds/"
+approach = "Realistic"
+classification = "Fullstack"
+database = "Postgres"
+database_os = "Linux"
+os = "Linux"
+orm = "Raw"
+platform = ".NET"
+webserver = "Wired.IO"
+versus = "None"
+
+[unhinged]
+urls.plaintext = "/plaintext"
+urls.json = "/json"
+urls.db = "/db"
+urls.query = "/queries/"
+urls.update = "/updates/"
+urls.fortune = "/fortunes"
+urls.cached_query = "/cached-worlds/"
+approach = "Realistic"
+classification = "Fullstack"
+database = "Postgres"
+database_os = "Linux"
+os = "Linux"
+orm = "Raw"
+platform = ".NET"
+webserver = "Unhinged"
+versus = "None"
diff --git a/frameworks/CSharp/genhttp/genhttp-kestrel.dockerfile b/frameworks/CSharp/genhttp/genhttp-kestrel.dockerfile
index e345a538f34..bc85d2b34e0 100644
--- a/frameworks/CSharp/genhttp/genhttp-kestrel.dockerfile
+++ b/frameworks/CSharp/genhttp/genhttp-kestrel.dockerfile
@@ -1,8 +1,7 @@
FROM mcr.microsoft.com/dotnet/sdk:10.0-alpine AS build
WORKDIR /source
-ENV GENHTTP_ENGINE_NAME=KESTREL
-ENV GENHTTP_ENGINE_PACKAGE=GenHTTP.Core.Kestrel
+ENV GENHTTP_ENGINE_NAME=Kestrel
# copy csproj and restore as distinct layers
COPY Benchmarks/*.csproj .
@@ -17,13 +16,12 @@ FROM mcr.microsoft.com/dotnet/runtime-deps:10.0-alpine
ENV DOTNET_GCDynamicAdaptationMode=0 \
DOTNET_EnableDiagnostics=0 \
- COMPlus_EnableDiagnostics=0 \
- COMPlus_DbgEnableMiniDump=0 \
- COMPlus_DbgEnableMiniDumpCollection=0 \
- COMPlus_DbgMiniDumpType=0 \
- DOTNET_TieredPGO=0 \
- DOTNET_TC_QuickJitForLoops=1 \
- DOTNET_TC_QuickJit=1
+ DOTNET_TieredPGO=1 \
+ DOTNET_TC_QuickJitForLoops=0 \
+ DOTNET_ReadyToRun=0 \
+ DOTNET_TieredCompilation=0 \
+ DOTNET_gcServer=1 \
+ DB_CONNECTION="Server=tfb-database;Database=hello_world;User Id=benchmarkdbuser;Password=benchmarkdbpass;SSL Mode=Disable;Maximum Pool Size=18;Enlist=false;Max Auto Prepare=4;Multiplexing=true;Write Coalescing Buffer Threshold Bytes=1000;"
WORKDIR /app
COPY --from=build /app .
diff --git a/frameworks/CSharp/genhttp/genhttp-unhinged.dockerfile b/frameworks/CSharp/genhttp/genhttp-unhinged.dockerfile
new file mode 100644
index 00000000000..ba540aa46d0
--- /dev/null
+++ b/frameworks/CSharp/genhttp/genhttp-unhinged.dockerfile
@@ -0,0 +1,31 @@
+FROM mcr.microsoft.com/dotnet/sdk:10.0-alpine AS build
+WORKDIR /source
+
+ENV GENHTTP_ENGINE_NAME=Unhinged
+
+# copy csproj and restore as distinct layers
+COPY Benchmarks/*.csproj .
+RUN dotnet restore -r linux-musl-x64
+
+# copy and publish app and libraries
+COPY Benchmarks/ .
+RUN dotnet publish -c release -o /app -r linux-musl-x64 --no-restore --self-contained
+
+# final stage/image
+FROM mcr.microsoft.com/dotnet/runtime-deps:10.0-alpine
+
+ENV DOTNET_GCDynamicAdaptationMode=0 \
+ DOTNET_EnableDiagnostics=0 \
+ DOTNET_TieredPGO=1 \
+ DOTNET_TC_QuickJitForLoops=0 \
+ DOTNET_ReadyToRun=0 \
+ DOTNET_TieredCompilation=0 \
+ DOTNET_gcServer=1 \
+ DB_CONNECTION="Server=tfb-database;Database=hello_world;User Id=benchmarkdbuser;Password=benchmarkdbpass;SSL Mode=Disable;Maximum Pool Size=18;Enlist=false;Max Auto Prepare=4;Multiplexing=true;Write Coalescing Buffer Threshold Bytes=1000;"
+
+WORKDIR /app
+COPY --from=build /app .
+
+ENTRYPOINT ["./Benchmarks"]
+
+EXPOSE 8080
diff --git a/frameworks/CSharp/genhttp/genhttp-wired.dockerfile b/frameworks/CSharp/genhttp/genhttp-wired.dockerfile
new file mode 100644
index 00000000000..9011e6ef2f0
--- /dev/null
+++ b/frameworks/CSharp/genhttp/genhttp-wired.dockerfile
@@ -0,0 +1,31 @@
+FROM mcr.microsoft.com/dotnet/sdk:10.0-alpine AS build
+WORKDIR /source
+
+ENV GENHTTP_ENGINE_NAME=Wired
+
+# copy csproj and restore as distinct layers
+COPY Benchmarks/*.csproj .
+RUN dotnet restore -r linux-musl-x64
+
+# copy and publish app and libraries
+COPY Benchmarks/ .
+RUN dotnet publish -c release -o /app -r linux-musl-x64 --no-restore --self-contained
+
+# final stage/image
+FROM mcr.microsoft.com/dotnet/runtime-deps:10.0-alpine
+
+ENV DOTNET_GCDynamicAdaptationMode=0 \
+ DOTNET_EnableDiagnostics=0 \
+ DOTNET_TieredPGO=1 \
+ DOTNET_TC_QuickJitForLoops=0 \
+ DOTNET_ReadyToRun=0 \
+ DOTNET_TieredCompilation=0 \
+ DOTNET_gcServer=1 \
+ DB_CONNECTION="Server=tfb-database;Database=hello_world;User Id=benchmarkdbuser;Password=benchmarkdbpass;SSL Mode=Disable;Maximum Pool Size=18;Enlist=false;Max Auto Prepare=4;Multiplexing=true;Write Coalescing Buffer Threshold Bytes=1000;"
+
+WORKDIR /app
+COPY --from=build /app .
+
+ENTRYPOINT ["./Benchmarks"]
+
+EXPOSE 8080
diff --git a/frameworks/CSharp/genhttp/genhttp.dockerfile b/frameworks/CSharp/genhttp/genhttp.dockerfile
index 9414123202e..2280d4c2b53 100644
--- a/frameworks/CSharp/genhttp/genhttp.dockerfile
+++ b/frameworks/CSharp/genhttp/genhttp.dockerfile
@@ -1,8 +1,7 @@
FROM mcr.microsoft.com/dotnet/sdk:10.0-alpine AS build
WORKDIR /source
-ENV GENHTTP_ENGINE_NAME=INTERNAL
-ENV GENHTTP_ENGINE_PACKAGE=GenHTTP.Core
+ENV GENHTTP_ENGINE_NAME=Internal
# copy csproj and restore as distinct layers
COPY Benchmarks/*.csproj .
@@ -17,13 +16,12 @@ FROM mcr.microsoft.com/dotnet/runtime-deps:10.0-alpine
ENV DOTNET_GCDynamicAdaptationMode=0 \
DOTNET_EnableDiagnostics=0 \
- COMPlus_EnableDiagnostics=0 \
- COMPlus_DbgEnableMiniDump=0 \
- COMPlus_DbgEnableMiniDumpCollection=0 \
- COMPlus_DbgMiniDumpType=0 \
- DOTNET_TieredPGO=0 \
- DOTNET_TC_QuickJitForLoops=1 \
- DOTNET_TC_QuickJit=1
+ DOTNET_TieredPGO=1 \
+ DOTNET_TC_QuickJitForLoops=0 \
+ DOTNET_ReadyToRun=0 \
+ DOTNET_TieredCompilation=0 \
+ DOTNET_gcServer=1 \
+ DB_CONNECTION="Server=tfb-database;Database=hello_world;User Id=benchmarkdbuser;Password=benchmarkdbpass;SSL Mode=Disable;Maximum Pool Size=18;Enlist=false;Max Auto Prepare=4;Multiplexing=true;Write Coalescing Buffer Threshold Bytes=1000;"
WORKDIR /app
COPY --from=build /app .
diff --git a/frameworks/CSharp/wiredio/UnhGHttp/Program.cs b/frameworks/CSharp/wiredio/UnhGHttp/Program.cs
deleted file mode 100644
index 03d8a523ff3..00000000000
--- a/frameworks/CSharp/wiredio/UnhGHttp/Program.cs
+++ /dev/null
@@ -1,40 +0,0 @@
-using System.Text.Json;
-using GenHTTP.Api.Protocol;
-using GenHTTP.Modules.IO;
-using GenHTTP.Modules.Layouting;
-using GenHTTP.Modules.Layouting.Provider;
-using Unhinged;
-using Unhinged.GenHttp.Experimental;
-
-internal class Program
-{
- public static void Main(string[] args)
- {
- var builder = UnhingedEngine
- .CreateBuilder()
- .SetNWorkersSolver(() => Environment.ProcessorCount)
- .SetBacklog(16384)
- .SetMaxEventsPerWake(512)
- .SetMaxNumberConnectionsPerWorker(512)
- .SetPort(8080)
- .SetSlabSizes(32 * 1024, 16 * 1024)
- .Map(CreateLayoutBuilder());
-
- var engine = builder.Build();
- engine.Run();
- }
-
- private static LayoutBuilder CreateLayoutBuilder() =>
- Layout
- .Create()
- .Add("/plaintext", Content.From(Resource.FromString("Hello, World!")))
-
- .Add("/json", Content.From(
- Resource.FromString(JsonSerializer.Serialize(new JsonMessage { message = "Hello, World!" }))
- .Type(new FlexibleContentType("application/json"))));
-}
-
-public class JsonMessage
-{
- public string message { get; set; }
-}
\ No newline at end of file
diff --git a/frameworks/CSharp/wiredio/UnhGHttp/UnhGHttp.csproj b/frameworks/CSharp/wiredio/UnhGHttp/UnhGHttp.csproj
deleted file mode 100644
index 09308ff4747..00000000000
--- a/frameworks/CSharp/wiredio/UnhGHttp/UnhGHttp.csproj
+++ /dev/null
@@ -1,23 +0,0 @@
-
-
-
- Exe
- net9.0
- enable
- enable
-
- true
- true
- true
- true
-
- linux-musl-x64
-
-
-
-
-
-
-
-
-
diff --git a/frameworks/CSharp/wiredio/benchmark_config.json b/frameworks/CSharp/wiredio/benchmark_config.json
index 2d9ebf4896f..dd173b57d0b 100644
--- a/frameworks/CSharp/wiredio/benchmark_config.json
+++ b/frameworks/CSharp/wiredio/benchmark_config.json
@@ -53,23 +53,6 @@
"database_os": "Linux",
"display_name": "Unhinged [p]",
"notes": "epoll"
- },
- "gen": {
- "plaintext_url": "/plaintext",
- "json_url": "/json",
- "port": 8080,
- "approach": "Realistic",
- "classification": "Fullstack",
- "database": "None",
- "framework": "GenHttp",
- "language": "C#",
- "orm": "None",
- "platform": ".NET",
- "webserver": "Unhinged",
- "os": "Linux",
- "database_os": "Linux",
- "display_name": "genhttp [Unhinged]",
- "notes": "Adapter"
}
}
]
diff --git a/frameworks/CSharp/wiredio/config.toml b/frameworks/CSharp/wiredio/config.toml
index 0b973f1f1fb..c0e5b1efa44 100644
--- a/frameworks/CSharp/wiredio/config.toml
+++ b/frameworks/CSharp/wiredio/config.toml
@@ -36,15 +36,3 @@ orm = "None"
platform = ".NET"
webserver = "Unhinged"
versus = "None"
-
-[gen]
-urls.plaintext = "/plaintext"
-urls.json = "/json"
-approach = "Realistic"
-classification = "Fullstack"
-os = "Linux"
-database_os = "Linux"
-orm = "None"
-platform = ".NET"
-webserver = "Unhinged"
-versus = "None"
\ No newline at end of file
diff --git a/frameworks/CSharp/wiredio/wiredio-gen.dockerfile b/frameworks/CSharp/wiredio/wiredio-gen.dockerfile
deleted file mode 100644
index 5436527f964..00000000000
--- a/frameworks/CSharp/wiredio/wiredio-gen.dockerfile
+++ /dev/null
@@ -1,24 +0,0 @@
-FROM mcr.microsoft.com/dotnet/sdk:9.0-alpine AS build
-WORKDIR /source
-
-# copy csproj and restore as distinct layers
-COPY UnhGHttp/*.csproj .
-RUN dotnet restore -r linux-musl-x64
-
-# copy and publish app and libraries
-COPY UnhGHttp/ .
-RUN dotnet publish -c release -o /app -r linux-musl-x64 --no-restore --self-contained
-
-# final stage/image
-FROM mcr.microsoft.com/dotnet/runtime-deps:9.0-alpine
-
-ENV DOTNET_GCDynamicAdaptationMode=0
-ENV DOTNET_ReadyToRun=0
-ENV DOTNET_HillClimbing_Disable=1
-
-WORKDIR /app
-COPY --from=build /app .
-
-ENTRYPOINT ["./UnhGHttp"]
-
-EXPOSE 8080