Эх сурвалжийг харах

Bump Wired.IO to 9.5.3, supports pipelined http. Add platform benchmark (#10180)

* Bump Wired.IO to 9.5.0, supports pipelined http. Add platform benchmark

* Header mismatch fix

* Tune nº of working threads, add comments.

* bump unhinged to 9.0.1

* Update README and maintainers

* Change to linux-musl-x64

* bump to 9.5.3

* bump to 9.5.3
Diogo Martins 4 өдөр өмнө
parent
commit
d08bb821eb

+ 0 - 23
frameworks/CSharp/wiredio/Benchmarks/Benchmarks.csproj

@@ -1,23 +0,0 @@
-<Project Sdk="Microsoft.NET.Sdk">
-
-  <PropertyGroup>
-    <OutputType>Exe</OutputType>
-    <TargetFramework>net9.0</TargetFramework>
-    <LangVersion>13.0</LangVersion>
-    <ImplicitUsings>enable</ImplicitUsings>
-    <Nullable>enable</Nullable>
-
-    <ServerGarbageCollection>true</ServerGarbageCollection>
-    <TieredPGO>true</TieredPGO>
-
-    <!-- Required for self-contained publish -->
-    <RuntimeIdentifier>linux-musl-x64</RuntimeIdentifier>
-    <SelfContained>true</SelfContained>
-    
-  </PropertyGroup>
-
-  <ItemGroup>
-    <PackageReference Include="Wired.IO" Version="9.2.0" />
-  </ItemGroup>
-
-</Project>

+ 0 - 32
frameworks/CSharp/wiredio/Benchmarks/Program.cs

@@ -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();

+ 4 - 4
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)

+ 20 - 2
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"
       }
     }
   ]
-}
+}

+ 12 - 0
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"

+ 26 - 0
frameworks/CSharp/wiredio/src/Fullstack/Fullstack.csproj

@@ -0,0 +1,26 @@
+<Project Sdk="Microsoft.NET.Sdk">
+
+    <PropertyGroup>
+        <OutputType>Exe</OutputType>
+        <TargetFramework>net9.0</TargetFramework>
+        <ImplicitUsings>enable</ImplicitUsings>
+        <Nullable>enable</Nullable>
+        <AllowUnsafeBlocks>true</AllowUnsafeBlocks>
+        <IsTestAssetProject>true</IsTestAssetProject>
+        <ServerGarbageCollection>true</ServerGarbageCollection>
+        <TieredPGO>true</TieredPGO>
+
+        <!-- Required for self-contained publish -->
+        <RuntimeIdentifier>linux-musl-x64</RuntimeIdentifier>
+        <SelfContained>true</SelfContained>
+    </PropertyGroup>
+
+    <ItemGroup Condition="$(PublishAot) == 'true'">
+        <RuntimeHostConfigurationOption Include="System.Threading.ThreadPool.HillClimbing.Disable" Value="true" />
+    </ItemGroup>
+
+    <ItemGroup>
+        <PackageReference Include="Wired.IO" Version="9.5.3" />
+    </ItemGroup>
+
+</Project>

+ 60 - 0
frameworks/CSharp/wiredio/src/Fullstack/Program.cs

@@ -0,0 +1,60 @@
+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)
+            .NoScopedEndpoints()
+            .MapGet("/json", _ => 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", _ => async ctx =>
+            {
+                ctx
+                    .Respond()
+                    .Type("text/plain"u8)
+                    .Content(_plainTextBody);
+            })
+            .Build()
+            .RunAsync();
+    }
+    
+    private static ReadOnlySpan<byte> _plainTextBody => "Hello, World!"u8;
+    private const string JsonBody = "Hello, World!";
+    
+    [ThreadStatic]
+    private static Utf8JsonWriter? t_writer;
+    private static readonly Action<PipeWriter, JsonMessage> 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 { }

+ 25 - 0
frameworks/CSharp/wiredio/src/Platform/Platform.csproj

@@ -0,0 +1,25 @@
+<Project Sdk="Microsoft.NET.Sdk">
+
+    <PropertyGroup>
+        <OutputType>Exe</OutputType>
+        <TargetFramework>net9.0</TargetFramework>
+        <ImplicitUsings>enable</ImplicitUsings>
+        <Nullable>enable</Nullable>
+        <AllowUnsafeBlocks>true</AllowUnsafeBlocks>
+        <IsTestAssetProject>true</IsTestAssetProject>
+        <ServerGarbageCollection>true</ServerGarbageCollection>
+        <TieredPGO>true</TieredPGO>
+
+        <!-- Required for self-contained publish -->
+        <RuntimeIdentifier>linux-musl-x64</RuntimeIdentifier>
+        <SelfContained>true</SelfContained>
+    </PropertyGroup>
+
+    <ItemGroup Condition="$(PublishAot) == 'true'">
+        <RuntimeHostConfigurationOption Include="System.Threading.ThreadPool.HillClimbing.Disable" Value="true" />
+    </ItemGroup>
+
+    <ItemGroup>
+      <PackageReference Include="Unhinged" Version="9.0.1" />
+    </ItemGroup>
+</Project>

+ 99 - 0
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);
+    }
+}

+ 22 - 0
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"]

+ 17 - 19
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"]