diff --git a/frameworks/CSharp/wiredio/Benchmarks/Benchmarks.csproj b/frameworks/CSharp/wiredio/Benchmarks/Benchmarks.csproj deleted file mode 100644 index 627c09fb8c8..00000000000 --- a/frameworks/CSharp/wiredio/Benchmarks/Benchmarks.csproj +++ /dev/null @@ -1,23 +0,0 @@ - - - - Exe - net9.0 - 13.0 - enable - enable - - true - true - - - linux-musl-x64 - true - - - - - - - - diff --git a/frameworks/CSharp/wiredio/Benchmarks/Program.cs b/frameworks/CSharp/wiredio/Benchmarks/Program.cs deleted file mode 100644 index 8a41b222386..00000000000 --- a/frameworks/CSharp/wiredio/Benchmarks/Program.cs +++ /dev/null @@ -1,32 +0,0 @@ -using System.Net; -using System.Text.Json; -using Wired.IO.App; -using Wired.IO.Http11.Response.Content; -using Wired.IO.Protocol.Response; -using StringContent = Wired.IO.Http11.Response.Content.StringContent; - -var builder = WiredApp.CreateBuilder(); - -await builder - .Endpoint(IPAddress.Any, 8080) - .MapGet("/plaintext", scope => context => - { - context - .Respond() - .Status(ResponseStatus.Ok) - .Content(new StringContent("Hello, World!")) - .Type("text/plain"); - }) - .MapGet("/json", scope => context => - { - context - .Respond() - .Status(ResponseStatus.Ok) - .Content(new JsonContent(new - { - Message = "Hello, World!" - }, JsonSerializerOptions.Default)) - .Type("application/json"); - }) - .Build() - .RunAsync(); \ No newline at end of file diff --git a/frameworks/CSharp/wiredio/README.md b/frameworks/CSharp/wiredio/README.md index 4f137ae45c2..7b852008832 100644 --- a/frameworks/CSharp/wiredio/README.md +++ b/frameworks/CSharp/wiredio/README.md @@ -10,13 +10,13 @@ See the [Wired.IO Documentation](https://mda2av.github.io/Wired.IO.Docs/) for mo **Platforms** -* .NET 8/9 +* .NET 9 **Web Servers** * [Wired.IO](https://github.com/MDA2AV/Wired.IO) -## Paths & Source for Tests +**Engines** -* [Plaintext](Benchmarks/Program.cs): "/plaintext" -* [JSON](Benchmarks/Program.cs): "/json" +* [Wired.IO](https://github.com/MDA2AV/Wired.IO) +* [Unhinged](https://github.com/MDA2AV/Unhinged) \ No newline at end of file diff --git a/frameworks/CSharp/wiredio/benchmark_config.json b/frameworks/CSharp/wiredio/benchmark_config.json index f136d0588b7..e74c7696415 100644 --- a/frameworks/CSharp/wiredio/benchmark_config.json +++ b/frameworks/CSharp/wiredio/benchmark_config.json @@ -1,5 +1,6 @@ { "framework": "wiredio", + "maintainers": ["MDA2AV"], "tests": [ { "default": { @@ -17,8 +18,25 @@ "os": "Linux", "database_os": "Linux", "display_name": "Wired.IO", - "notes": "Only plaintext and JSON benchmarks implemented" + "notes": "Only plaintext and JSON benchmarks implemented yet" + }, + "plt": { + "plaintext_url": "/plaintext", + "json_url": "/json", + "port": 8080, + "approach": "Realistic", + "classification": "Platform", + "database": "None", + "framework": "Unhinged", + "language": "C#", + "orm": "None", + "platform": ".NET", + "webserver": "Unhinged", + "os": "Linux", + "database_os": "Linux", + "display_name": "Wired.IO [Unhinged]", + "notes": "Not a framework" } } ] -} \ No newline at end of file +} diff --git a/frameworks/CSharp/wiredio/config.toml b/frameworks/CSharp/wiredio/config.toml index 55e724fde37..fe292c91330 100644 --- a/frameworks/CSharp/wiredio/config.toml +++ b/frameworks/CSharp/wiredio/config.toml @@ -12,3 +12,15 @@ orm = "None" platform = ".NET" webserver = "Wired.IO" versus = "None" + +[plt] +urls.plaintext = "/plaintext" +urls.json = "/json" +approach = "Realistic" +classification = "Platform" +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/src/Fullstack/Fullstack.csproj b/frameworks/CSharp/wiredio/src/Fullstack/Fullstack.csproj new file mode 100644 index 00000000000..437ed45a428 --- /dev/null +++ b/frameworks/CSharp/wiredio/src/Fullstack/Fullstack.csproj @@ -0,0 +1,26 @@ + + + + Exe + net9.0 + enable + enable + true + true + true + true + + + linux-musl-x64 + true + + + + + + + + + + + \ No newline at end of file diff --git a/frameworks/CSharp/wiredio/src/Fullstack/Program.cs b/frameworks/CSharp/wiredio/src/Fullstack/Program.cs new file mode 100644 index 00000000000..9450ba93892 --- /dev/null +++ b/frameworks/CSharp/wiredio/src/Fullstack/Program.cs @@ -0,0 +1,59 @@ +using System.IO.Pipelines; +using System.Text.Json; +using System.Text.Json.Serialization; +using Wired.IO.App; + +namespace Fullstack; + +internal class Program +{ + public static async Task Main(string[] args) + { + var expressBuilder = WiredApp.CreateExpressBuilder(); + + await expressBuilder + .Port(8080) + .MapGet("/json", scope => async ctx => + { + var payload = new JsonMessage { Message = JsonBody }; + var myHandler = CreateBoundHandler(ctx.Writer, payload); + + ctx + .Respond() + .Type("application/json"u8) + .Content(myHandler, 27); + + }) + .MapGet("/plaintext", scope => async ctx => + { + ctx + .Respond() + .Type("text/plain"u8) + .Content(_plainTextBody); + }) + .Build() + .RunAsync(); + } + + private static ReadOnlySpan _plainTextBody => "Hello, World!"u8; + private const string JsonBody = "Hello, World!"; + + [ThreadStatic] + private static Utf8JsonWriter? t_writer; + private static readonly Action StaticHandler = HandleFast; + private static Action CreateBoundHandler(PipeWriter writer, JsonMessage message) => () => StaticHandler.Invoke(writer, message); + private static void HandleFast(PipeWriter writer, JsonMessage message) + { + var utf8JsonWriter = t_writer ??= new Utf8JsonWriter(writer, new JsonWriterOptions { SkipValidation = true }); + utf8JsonWriter.Reset(writer); + JsonSerializer.Serialize(utf8JsonWriter, message, SerializerContext.JsonMessage); + } + + private static readonly JsonContext SerializerContext = JsonContext.Default; +} + +public struct JsonMessage { public string Message { get; set; } } + +[JsonSourceGenerationOptions(GenerationMode = JsonSourceGenerationMode.Serialization | JsonSourceGenerationMode.Metadata)] +[JsonSerializable(typeof(JsonMessage))] +public partial class JsonContext : JsonSerializerContext { } \ No newline at end of file diff --git a/frameworks/CSharp/wiredio/src/Platform/Platform.csproj b/frameworks/CSharp/wiredio/src/Platform/Platform.csproj new file mode 100644 index 00000000000..1fd01bf2e63 --- /dev/null +++ b/frameworks/CSharp/wiredio/src/Platform/Platform.csproj @@ -0,0 +1,25 @@ + + + + Exe + net9.0 + enable + enable + true + true + true + true + + + linux-musl-x64 + true + + + + + + + + + + diff --git a/frameworks/CSharp/wiredio/src/Platform/Program.cs b/frameworks/CSharp/wiredio/src/Platform/Program.cs new file mode 100644 index 00000000000..0b76948f66f --- /dev/null +++ b/frameworks/CSharp/wiredio/src/Platform/Program.cs @@ -0,0 +1,99 @@ +// ReSharper disable always SuggestVarOrType_BuiltInTypes +// (var is avoided intentionally in this project so that concrete types are visible at call sites.) +// ReSharper disable always StackAllocInsideLoop + +using System.Runtime.CompilerServices; +using System.Text.Json; +using Unhinged; + +#pragma warning disable CA2014 + +/* (MDA2AV)Dev notes: + * + * Wired.IO Platform benchmark using [Unhinged - https://github.com/MDA2AV/Unhinged] epoll engine. + * + * This test was created purely for benchmark/comparison between .NET solutions. + * It should not be considered EVER as a go-to framework to build any kind of webserver! + * For such purpose please use the main Wired.IO framework [Wired.IO - https://github.com/MDA2AV/Wired.IO]. + * + * This benchmarks follows the JsonSerialization and PlainText rules imposed by the TechEmpower team. + * + * The Http parsing by the Unhinged engine is still naive(work in progress), yet it's development will not have any impact + * on these benchmarks results as the extra request parsing overhead is much smaller than the read/send syscalls'. + */ + +namespace Platform; + +[SkipLocalsInit] +internal static class Program +{ + public static void Main(string[] args) + { + var builder = UnhingedEngine + .CreateBuilder() + .SetPort(8080) + + + // Number of working threads + // Reasoning behind Environment.ProcessorCount / 2 + // It's the number of real cpu cores not cpu threads + // This can improve the cache hits on L1/L2 since only one thread + // is running per cpu core. + .SetNWorkersSolver(() => Environment.ProcessorCount/2) + + // Accept up to 16384 connections + .SetBacklog(16384) + + // Max 512 epoll events per wake (quite overkill) + .SetMaxEventsPerWake(512) + + // Max 1024 connection per thread + .SetMaxNumberConnectionsPerWorker(1024) + + // 32KB in and 16KB out slabs to handle 16 pipeline depth + .SetSlabSizes(32 * 1024, 16 * 1024) + .InjectRequestHandler(RequestHandler); + + var engine = builder.Build(); + engine.Run(); + } + + private static void RequestHandler(Connection connection) + { + // FNV-1a Hashed routes to avoid string allocations + if(connection.HashedRoute == 291830056) // /json + CommitJsonResponse(connection); + + else if (connection.HashedRoute == 3454831873) // /plaintext + CommitPlainTextResponse(connection); + } + + [ThreadStatic] private static Utf8JsonWriter? t_utf8JsonWriter; + private static readonly JsonContext SerializerContext = JsonContext.Default; + private static void CommitJsonResponse(Connection connection) + { + connection.WriteBuffer.WriteUnmanaged("HTTP/1.1 200 OK\r\n"u8 + + "Server: W\r\n"u8 + + "Content-Type: application/json; charset=UTF-8\r\n"u8 + + "Content-Length: 27\r\n"u8); + connection.WriteBuffer.WriteUnmanaged(DateHelper.HeaderBytes); + + t_utf8JsonWriter ??= new Utf8JsonWriter(connection.WriteBuffer, new JsonWriterOptions { SkipValidation = true }); + t_utf8JsonWriter.Reset(connection.WriteBuffer); + + // Creating(Allocating) a new JsonMessage every request + var message = new JsonMessage { Message = "Hello, World!" }; + // Serializing it every request + JsonSerializer.Serialize(t_utf8JsonWriter, message, SerializerContext.JsonMessage); + } + + private static void CommitPlainTextResponse(Connection connection) + { + connection.WriteBuffer.WriteUnmanaged("HTTP/1.1 200 OK\r\n"u8 + + "Server: W\r\n"u8 + + "Content-Type: text/plain\r\n"u8 + + "Content-Length: 13\r\n"u8); + connection.WriteBuffer.WriteUnmanaged(DateHelper.HeaderBytes); + connection.WriteBuffer.WriteUnmanaged("Hello, World!"u8); + } +} \ No newline at end of file diff --git a/frameworks/CSharp/wiredio/wiredio-plt.dockerfile b/frameworks/CSharp/wiredio/wiredio-plt.dockerfile new file mode 100644 index 00000000000..9bd497eec84 --- /dev/null +++ b/frameworks/CSharp/wiredio/wiredio-plt.dockerfile @@ -0,0 +1,22 @@ +# Build +FROM mcr.microsoft.com/dotnet/sdk:9.0-alpine AS build +RUN apk add --no-cache clang build-base zlib-dev linux-headers +WORKDIR /src +COPY src/Platform/ ./Platform/ +WORKDIR /src/Platform +RUN dotnet publish -c Release \ + -r linux-musl-x64 \ + --self-contained true \ + -p:PublishAot=true \ + -p:OptimizationPreference=Speed \ + -p:GarbageCollectionAdaptationMode=0 \ + -o /app/out + +# Runtime (musl) +FROM mcr.microsoft.com/dotnet/runtime-deps:9.0-alpine +ENV URLS=http://+:8080 +WORKDIR /app +COPY --from=build /app/out ./ +RUN chmod +x ./Platform +EXPOSE 8080 +ENTRYPOINT ["./Platform"] diff --git a/frameworks/CSharp/wiredio/wiredio.dockerfile b/frameworks/CSharp/wiredio/wiredio.dockerfile index 8c473477a61..2c6d853b0a2 100644 --- a/frameworks/CSharp/wiredio/wiredio.dockerfile +++ b/frameworks/CSharp/wiredio/wiredio.dockerfile @@ -1,24 +1,22 @@ +# Build FROM mcr.microsoft.com/dotnet/sdk:9.0-alpine AS build -WORKDIR /source +RUN apk add --no-cache clang build-base zlib-dev linux-headers +WORKDIR /src +COPY src/Fullstack/ ./Fullstack/ +WORKDIR /src/Fullstack +RUN dotnet publish -c Release \ + -r linux-musl-x64 \ + --self-contained true \ + -p:PublishAot=true \ + -p:OptimizationPreference=Speed \ + -p:GarbageCollectionAdaptationMode=0 \ + -o /app/out -# 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 +# Runtime (musl) FROM mcr.microsoft.com/dotnet/runtime-deps:9.0-alpine - -ENV DOTNET_GCDynamicAdaptationMode=0 -ENV DOTNET_ReadyToRun=0 -ENV DOTNET_HillClimbing_Disable=1 - +ENV URLS=http://+:8080 WORKDIR /app -COPY --from=build /app . - -ENTRYPOINT ["./Benchmarks"] - +COPY --from=build /app/out ./ +RUN chmod +x ./Fullstack EXPOSE 8080 +ENTRYPOINT ["./Fullstack"]