Skip to content

Commit d08bb82

Browse files
authored
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
1 parent 43edfc1 commit d08bb82

File tree

11 files changed

+285
-80
lines changed

11 files changed

+285
-80
lines changed

frameworks/CSharp/wiredio/Benchmarks/Benchmarks.csproj

Lines changed: 0 additions & 23 deletions
This file was deleted.

frameworks/CSharp/wiredio/Benchmarks/Program.cs

Lines changed: 0 additions & 32 deletions
This file was deleted.

frameworks/CSharp/wiredio/README.md

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -10,13 +10,13 @@ See the [Wired.IO Documentation](https://mda2av.github.io/Wired.IO.Docs/) for mo
1010

1111
**Platforms**
1212

13-
* .NET 8/9
13+
* .NET 9
1414

1515
**Web Servers**
1616

1717
* [Wired.IO](https://github.com/MDA2AV/Wired.IO)
1818

19-
## Paths & Source for Tests
19+
**Engines**
2020

21-
* [Plaintext](Benchmarks/Program.cs): "/plaintext"
22-
* [JSON](Benchmarks/Program.cs): "/json"
21+
* [Wired.IO](https://github.com/MDA2AV/Wired.IO)
22+
* [Unhinged](https://github.com/MDA2AV/Unhinged)

frameworks/CSharp/wiredio/benchmark_config.json

Lines changed: 20 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
{
22
"framework": "wiredio",
3+
"maintainers": ["MDA2AV"],
34
"tests": [
45
{
56
"default": {
@@ -17,8 +18,25 @@
1718
"os": "Linux",
1819
"database_os": "Linux",
1920
"display_name": "Wired.IO",
20-
"notes": "Only plaintext and JSON benchmarks implemented"
21+
"notes": "Only plaintext and JSON benchmarks implemented yet"
22+
},
23+
"plt": {
24+
"plaintext_url": "/plaintext",
25+
"json_url": "/json",
26+
"port": 8080,
27+
"approach": "Realistic",
28+
"classification": "Platform",
29+
"database": "None",
30+
"framework": "Unhinged",
31+
"language": "C#",
32+
"orm": "None",
33+
"platform": ".NET",
34+
"webserver": "Unhinged",
35+
"os": "Linux",
36+
"database_os": "Linux",
37+
"display_name": "Wired.IO [Unhinged]",
38+
"notes": "Not a framework"
2139
}
2240
}
2341
]
24-
}
42+
}

frameworks/CSharp/wiredio/config.toml

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,3 +12,15 @@ orm = "None"
1212
platform = ".NET"
1313
webserver = "Wired.IO"
1414
versus = "None"
15+
16+
[plt]
17+
urls.plaintext = "/plaintext"
18+
urls.json = "/json"
19+
approach = "Realistic"
20+
classification = "Platform"
21+
os = "Linux"
22+
database_os = "Linux"
23+
orm = "None"
24+
platform = ".NET"
25+
webserver = "Unhinged"
26+
versus = "None"
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
<Project Sdk="Microsoft.NET.Sdk">
2+
3+
<PropertyGroup>
4+
<OutputType>Exe</OutputType>
5+
<TargetFramework>net9.0</TargetFramework>
6+
<ImplicitUsings>enable</ImplicitUsings>
7+
<Nullable>enable</Nullable>
8+
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
9+
<IsTestAssetProject>true</IsTestAssetProject>
10+
<ServerGarbageCollection>true</ServerGarbageCollection>
11+
<TieredPGO>true</TieredPGO>
12+
13+
<!-- Required for self-contained publish -->
14+
<RuntimeIdentifier>linux-musl-x64</RuntimeIdentifier>
15+
<SelfContained>true</SelfContained>
16+
</PropertyGroup>
17+
18+
<ItemGroup Condition="$(PublishAot) == 'true'">
19+
<RuntimeHostConfigurationOption Include="System.Threading.ThreadPool.HillClimbing.Disable" Value="true" />
20+
</ItemGroup>
21+
22+
<ItemGroup>
23+
<PackageReference Include="Wired.IO" Version="9.5.3" />
24+
</ItemGroup>
25+
26+
</Project>
Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
using System.IO.Pipelines;
2+
using System.Text.Json;
3+
using System.Text.Json.Serialization;
4+
using Wired.IO.App;
5+
6+
namespace Fullstack;
7+
8+
internal class Program
9+
{
10+
public static async Task Main(string[] args)
11+
{
12+
var expressBuilder = WiredApp.CreateExpressBuilder();
13+
14+
await expressBuilder
15+
.Port(8080)
16+
.NoScopedEndpoints()
17+
.MapGet("/json", _ => async ctx =>
18+
{
19+
var payload = new JsonMessage { Message = JsonBody };
20+
var myHandler = CreateBoundHandler(ctx.Writer, payload);
21+
22+
ctx
23+
.Respond()
24+
.Type("application/json"u8)
25+
.Content(myHandler, 27);
26+
27+
})
28+
.MapGet("/plaintext", _ => async ctx =>
29+
{
30+
ctx
31+
.Respond()
32+
.Type("text/plain"u8)
33+
.Content(_plainTextBody);
34+
})
35+
.Build()
36+
.RunAsync();
37+
}
38+
39+
private static ReadOnlySpan<byte> _plainTextBody => "Hello, World!"u8;
40+
private const string JsonBody = "Hello, World!";
41+
42+
[ThreadStatic]
43+
private static Utf8JsonWriter? t_writer;
44+
private static readonly Action<PipeWriter, JsonMessage> StaticHandler = HandleFast;
45+
private static Action CreateBoundHandler(PipeWriter writer, JsonMessage message) => () => StaticHandler.Invoke(writer, message);
46+
private static void HandleFast(PipeWriter writer, JsonMessage message)
47+
{
48+
var utf8JsonWriter = t_writer ??= new Utf8JsonWriter(writer, new JsonWriterOptions { SkipValidation = true });
49+
utf8JsonWriter.Reset(writer);
50+
JsonSerializer.Serialize(utf8JsonWriter, message, SerializerContext.JsonMessage);
51+
}
52+
53+
private static readonly JsonContext SerializerContext = JsonContext.Default;
54+
}
55+
56+
public struct JsonMessage { public string Message { get; set; } }
57+
58+
[JsonSourceGenerationOptions(GenerationMode = JsonSourceGenerationMode.Serialization | JsonSourceGenerationMode.Metadata)]
59+
[JsonSerializable(typeof(JsonMessage))]
60+
public partial class JsonContext : JsonSerializerContext { }
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
<Project Sdk="Microsoft.NET.Sdk">
2+
3+
<PropertyGroup>
4+
<OutputType>Exe</OutputType>
5+
<TargetFramework>net9.0</TargetFramework>
6+
<ImplicitUsings>enable</ImplicitUsings>
7+
<Nullable>enable</Nullable>
8+
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
9+
<IsTestAssetProject>true</IsTestAssetProject>
10+
<ServerGarbageCollection>true</ServerGarbageCollection>
11+
<TieredPGO>true</TieredPGO>
12+
13+
<!-- Required for self-contained publish -->
14+
<RuntimeIdentifier>linux-musl-x64</RuntimeIdentifier>
15+
<SelfContained>true</SelfContained>
16+
</PropertyGroup>
17+
18+
<ItemGroup Condition="$(PublishAot) == 'true'">
19+
<RuntimeHostConfigurationOption Include="System.Threading.ThreadPool.HillClimbing.Disable" Value="true" />
20+
</ItemGroup>
21+
22+
<ItemGroup>
23+
<PackageReference Include="Unhinged" Version="9.0.1" />
24+
</ItemGroup>
25+
</Project>
Lines changed: 99 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,99 @@
1+
// ReSharper disable always SuggestVarOrType_BuiltInTypes
2+
// (var is avoided intentionally in this project so that concrete types are visible at call sites.)
3+
// ReSharper disable always StackAllocInsideLoop
4+
5+
using System.Runtime.CompilerServices;
6+
using System.Text.Json;
7+
using Unhinged;
8+
9+
#pragma warning disable CA2014
10+
11+
/* (MDA2AV)Dev notes:
12+
*
13+
* Wired.IO Platform benchmark using [Unhinged - https://github.com/MDA2AV/Unhinged] epoll engine.
14+
*
15+
* This test was created purely for benchmark/comparison between .NET solutions.
16+
* It should not be considered EVER as a go-to framework to build any kind of webserver!
17+
* For such purpose please use the main Wired.IO framework [Wired.IO - https://github.com/MDA2AV/Wired.IO].
18+
*
19+
* This benchmarks follows the JsonSerialization and PlainText rules imposed by the TechEmpower team.
20+
*
21+
* The Http parsing by the Unhinged engine is still naive(work in progress), yet it's development will not have any impact
22+
* on these benchmarks results as the extra request parsing overhead is much smaller than the read/send syscalls'.
23+
*/
24+
25+
namespace Platform;
26+
27+
[SkipLocalsInit]
28+
internal static class Program
29+
{
30+
public static void Main(string[] args)
31+
{
32+
var builder = UnhingedEngine
33+
.CreateBuilder()
34+
.SetPort(8080)
35+
36+
37+
// Number of working threads
38+
// Reasoning behind Environment.ProcessorCount / 2
39+
// It's the number of real cpu cores not cpu threads
40+
// This can improve the cache hits on L1/L2 since only one thread
41+
// is running per cpu core.
42+
.SetNWorkersSolver(() => Environment.ProcessorCount/2)
43+
44+
// Accept up to 16384 connections
45+
.SetBacklog(16384)
46+
47+
// Max 512 epoll events per wake (quite overkill)
48+
.SetMaxEventsPerWake(512)
49+
50+
// Max 1024 connection per thread
51+
.SetMaxNumberConnectionsPerWorker(1024)
52+
53+
// 32KB in and 16KB out slabs to handle 16 pipeline depth
54+
.SetSlabSizes(32 * 1024, 16 * 1024)
55+
.InjectRequestHandler(RequestHandler);
56+
57+
var engine = builder.Build();
58+
engine.Run();
59+
}
60+
61+
private static void RequestHandler(Connection connection)
62+
{
63+
// FNV-1a Hashed routes to avoid string allocations
64+
if(connection.HashedRoute == 291830056) // /json
65+
CommitJsonResponse(connection);
66+
67+
else if (connection.HashedRoute == 3454831873) // /plaintext
68+
CommitPlainTextResponse(connection);
69+
}
70+
71+
[ThreadStatic] private static Utf8JsonWriter? t_utf8JsonWriter;
72+
private static readonly JsonContext SerializerContext = JsonContext.Default;
73+
private static void CommitJsonResponse(Connection connection)
74+
{
75+
connection.WriteBuffer.WriteUnmanaged("HTTP/1.1 200 OK\r\n"u8 +
76+
"Server: W\r\n"u8 +
77+
"Content-Type: application/json; charset=UTF-8\r\n"u8 +
78+
"Content-Length: 27\r\n"u8);
79+
connection.WriteBuffer.WriteUnmanaged(DateHelper.HeaderBytes);
80+
81+
t_utf8JsonWriter ??= new Utf8JsonWriter(connection.WriteBuffer, new JsonWriterOptions { SkipValidation = true });
82+
t_utf8JsonWriter.Reset(connection.WriteBuffer);
83+
84+
// Creating(Allocating) a new JsonMessage every request
85+
var message = new JsonMessage { Message = "Hello, World!" };
86+
// Serializing it every request
87+
JsonSerializer.Serialize(t_utf8JsonWriter, message, SerializerContext.JsonMessage);
88+
}
89+
90+
private static void CommitPlainTextResponse(Connection connection)
91+
{
92+
connection.WriteBuffer.WriteUnmanaged("HTTP/1.1 200 OK\r\n"u8 +
93+
"Server: W\r\n"u8 +
94+
"Content-Type: text/plain\r\n"u8 +
95+
"Content-Length: 13\r\n"u8);
96+
connection.WriteBuffer.WriteUnmanaged(DateHelper.HeaderBytes);
97+
connection.WriteBuffer.WriteUnmanaged("Hello, World!"u8);
98+
}
99+
}
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
# Build
2+
FROM mcr.microsoft.com/dotnet/sdk:9.0-alpine AS build
3+
RUN apk add --no-cache clang build-base zlib-dev linux-headers
4+
WORKDIR /src
5+
COPY src/Platform/ ./Platform/
6+
WORKDIR /src/Platform
7+
RUN dotnet publish -c Release \
8+
-r linux-musl-x64 \
9+
--self-contained true \
10+
-p:PublishAot=true \
11+
-p:OptimizationPreference=Speed \
12+
-p:GarbageCollectionAdaptationMode=0 \
13+
-o /app/out
14+
15+
# Runtime (musl)
16+
FROM mcr.microsoft.com/dotnet/runtime-deps:9.0-alpine
17+
ENV URLS=http://+:8080
18+
WORKDIR /app
19+
COPY --from=build /app/out ./
20+
RUN chmod +x ./Platform
21+
EXPOSE 8080
22+
ENTRYPOINT ["./Platform"]

0 commit comments

Comments
 (0)