From f101c5fab9a56611a25b1117b9f5f20365891602 Mon Sep 17 00:00:00 2001 From: Shane Krueger Date: Sat, 20 Dec 2025 23:25:47 -0500 Subject: [PATCH 1/6] Add .NET benchmarks and update process management for go --- .gitignore | 5 +- benchmarks/dotnet-graphql.js | 33 ++++++ benchmarks/go-graphql.js | 62 ++++++++---- other-benchmarks/dotnet-gql/server.cs | 139 ++++++++++++++++++++++++++ 4 files changed, 220 insertions(+), 19 deletions(-) create mode 100644 benchmarks/dotnet-graphql.js create mode 100644 other-benchmarks/dotnet-gql/server.cs diff --git a/.gitignore b/.gitignore index 93e8a09c..8b8241e0 100644 --- a/.gitignore +++ b/.gitignore @@ -63,4 +63,7 @@ results100 results5 # Rust -other-benchmarks/rust-gql/target \ No newline at end of file +other-benchmarks/rust-gql/target + +# Go +other-benchmarks/go-gql/target diff --git a/benchmarks/dotnet-graphql.js b/benchmarks/dotnet-graphql.js new file mode 100644 index 00000000..a593cb41 --- /dev/null +++ b/benchmarks/dotnet-graphql.js @@ -0,0 +1,33 @@ +"use strict"; +const { spawn } = require("child_process"); +const path = require("path"); + +const forked = spawn("dotnet", ["run", "server.cs"], { + cwd: path.join(__dirname, "..", "other-benchmarks/dotnet-gql/"), + stdio: ["ignore", "pipe", "pipe"], + shell: false, +}); + +// Handle stdout/stderr +forked.stdout.on("data", (data) => { + console.log(`stdout: ${data}`); +}); + +forked.stderr.on("data", (data) => { + console.log(`stderr: ${data}`); +}); + +forked.on("error", (error) => { + console.log(`error: ${error.message}`); +}); + +// Forward signals to the child process +process.on("SIGINT", () => { + forked.kill("SIGINT"); +}); + +process.on("SIGTERM", () => { + forked.kill("SIGTERM"); +}); + +forked.on("exit", () => console.log("dotnet-graphql exited")); diff --git a/benchmarks/go-graphql.js b/benchmarks/go-graphql.js index eea7229b..cbb9ad3a 100644 --- a/benchmarks/go-graphql.js +++ b/benchmarks/go-graphql.js @@ -1,21 +1,47 @@ "use strict"; -const { exec } = require("child_process"); +const { spawn, execSync } = require("child_process"); const path = require("path"); +const fs = require("fs"); -const forked = exec( - "go run server.go", - { cwd: path.join(__dirname, "..", "other-benchmarks/go-gql/") }, - (error, stdout, stderr) => { - if (error) { - console.log(`error: ${error.message}`); - return; - } - if (stderr) { - console.log(`stderr: ${stderr}`); - return; - } - console.log(`stdout: ${stdout}`); - }, -); -console.log(path.join(__dirname, "..", "other-benchmarks/go-gql/server.go")); -forked.on("exit", () => console.log("exited")); +const cwd = path.join(__dirname, "..", "other-benchmarks/go-gql/"); +const binaryName = process.platform === "win32" ? "go-gql.exe" : "go-gql"; +const binaryPath = path.join(cwd, "target", binaryName); + +// Build the binary +try { + console.log("Building Go binary..."); + execSync(`go build -o target/${binaryName} .`, { cwd, stdio: "inherit" }); +} catch (error) { + console.log(`Build error: ${error.message}`); + process.exit(1); +} + +const forked = spawn(binaryPath, [], { + cwd, + stdio: ["ignore", "pipe", "pipe"], + shell: false, +}); + +// Handle stdout/stderr +forked.stdout.on("data", (data) => { + console.log(`stdout: ${data}`); +}); + +forked.stderr.on("data", (data) => { + console.log(`stderr: ${data}`); +}); + +forked.on("error", (error) => { + console.log(`error: ${error.message}`); +}); + +// Forward signals to the child process +process.on("SIGINT", () => { + forked.kill("SIGINT"); +}); + +process.on("SIGTERM", () => { + forked.kill("SIGTERM"); +}); + +forked.on("exit", () => console.log("go-graphql exited")); diff --git a/other-benchmarks/dotnet-gql/server.cs b/other-benchmarks/dotnet-gql/server.cs new file mode 100644 index 00000000..9d35ccc6 --- /dev/null +++ b/other-benchmarks/dotnet-gql/server.cs @@ -0,0 +1,139 @@ +#!/usr/bin/dotnet run +#:sdk Microsoft.NET.Sdk.Web +#:package GraphQL@8.8.2 +#:package GraphQL.SystemTextJson@8.8.2 +#:package GraphQL.Server.Transports.AspNetCore@8.3.3 +#:package GraphQL.Server.Ui.GraphiQL@8.3.3 +#:package Faker.Net@2.0.163 +#:property PublishAot=false + +using System.Security.Cryptography; +using System.Text; +using Faker; +using GraphQL; +using GraphQL.Types; +using Microsoft.Extensions.Logging; + +var builder = WebApplication.CreateBuilder(args); + +// Set logging to only show warnings and errors, output to stderr +builder.Logging.ClearProviders(); +builder.Logging.AddConsole(options => +{ + options.LogToStandardErrorThreshold = LogLevel.Trace; +}); +builder.Logging.SetMinimumLevel(LogLevel.Warning); + +// Add GraphQL services +builder.Services.AddGraphQL(b => b + .AddSystemTextJson() + .AddSchema() + .AddGraphTypes(typeof(AppSchema).Assembly)); + +var app = builder.Build(); + +// Handle SIGINT/SIGTERM for graceful shutdown +var lifetime = app.Services.GetRequiredService(); +Console.CancelKeyPress += (sender, e) => +{ + e.Cancel = true; + lifetime.StopApplication(); +}; + +// GraphQL endpoint +app.UseGraphQL("/graphql"); +app.UseGraphQLGraphiQL("/"); + +Console.WriteLine("GraphQL server listening on http://localhost:4001/graphql"); +Console.WriteLine("GraphiQL playground available at http://localhost:4001/"); +app.Run("http://localhost:4001"); + +// GraphQL Types +public class BookType : ObjectGraphType +{ + public BookType() + { + Field(x => x.Id); + Field(x => x.Name); + Field(x => x.NumPages); + } +} + +public class AuthorType : ObjectGraphType +{ + public AuthorType() + { + Field(x => x.Id); + Field(x => x.Name); + Field(x => x.Company); + Field("md5", x => x.Md5()); + Field>("books") + .Resolve(context => context.Source.Books); + } +} + +public class AppQuery : ObjectGraphType +{ + public AppQuery() + { + Field>("authors") + .Resolve(context => DataGenerator.GetAuthors()); + } +} + +public class AppSchema : Schema +{ + public AppSchema(IServiceProvider serviceProvider) : base(serviceProvider) + { + Query = serviceProvider.GetRequiredService(); + } +} + +// Data Models +public record Book(string Id, string Name, int NumPages); + +public record Author(string Id, string Name, string Company, List Books) +{ + public string Md5() + { + using var md5 = MD5.Create(); + var hash = md5.ComputeHash(Encoding.UTF8.GetBytes(Name)); + return BitConverter.ToString(hash).Replace("-", "").ToLowerInvariant(); + } +} + +// Data Generator using Faker.Net +public static class DataGenerator +{ + private static readonly List _authors = GenerateAuthors(); + + public static List GetAuthors() => _authors; + + private static List GenerateAuthors() + { + var random = new Random(4321); + var authors = new List(); + + for (int i = 0; i < 20; i++) + { + var books = new List(); + for (int k = 0; k < 3; k++) + { + books.Add(new Book( + Id: Guid.NewGuid().ToString(), + Name: Internet.DomainName(), + NumPages: RandomNumber.Next() + )); + } + + authors.Add(new Author( + Id: Guid.NewGuid().ToString(), + Name: Name.FullName(), + Company: Company.BS(), + Books: books + )); + } + + return authors; + } +} From 715b2ccaf119e3cd030399906c5dfc3a59dd3646 Mon Sep 17 00:00:00 2001 From: Shane Krueger Date: Sat, 20 Dec 2025 23:44:17 -0500 Subject: [PATCH 2/6] refactor: optimize MD5 hashing method for Author record --- other-benchmarks/dotnet-gql/server.cs | 17 +++++++++++++---- 1 file changed, 13 insertions(+), 4 deletions(-) diff --git a/other-benchmarks/dotnet-gql/server.cs b/other-benchmarks/dotnet-gql/server.cs index 9d35ccc6..c31d6d2a 100644 --- a/other-benchmarks/dotnet-gql/server.cs +++ b/other-benchmarks/dotnet-gql/server.cs @@ -94,11 +94,20 @@ public record Book(string Id, string Name, int NumPages); public record Author(string Id, string Name, string Company, List Books) { - public string Md5() + public string Md5() => Name.ToMd5Hash(); +} + +// MD5 Hashing Extension Method +static class Md5Extensions +{ + public static string ToMd5Hash(this string input) { - using var md5 = MD5.Create(); - var hash = md5.ComputeHash(Encoding.UTF8.GetBytes(Name)); - return BitConverter.ToString(hash).Replace("-", "").ToLowerInvariant(); + int bytes = Encoding.UTF8.GetMaxByteCount(input.Length); + Span utf8 = bytes <= 1024 ? stackalloc byte[bytes] : new byte[bytes]; + int actualBytes = Encoding.UTF8.GetBytes(input.AsSpan(), utf8); + Span hash = stackalloc byte[16]; + MD5.HashData(utf8[..actualBytes], hash); + return Convert.ToHexString(hash); } } From 951124d78d717b3013744614bd643d2c4d21399e Mon Sep 17 00:00:00 2001 From: Shane Krueger Date: Sat, 20 Dec 2025 23:58:07 -0500 Subject: [PATCH 3/6] refactor: remove fixed seed from random number generator in DataGenerator --- other-benchmarks/dotnet-gql/server.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/other-benchmarks/dotnet-gql/server.cs b/other-benchmarks/dotnet-gql/server.cs index c31d6d2a..834241ff 100644 --- a/other-benchmarks/dotnet-gql/server.cs +++ b/other-benchmarks/dotnet-gql/server.cs @@ -120,7 +120,6 @@ public static class DataGenerator private static List GenerateAuthors() { - var random = new Random(4321); var authors = new List(); for (int i = 0; i < 20; i++) From 2cb4cecd6a7738a3476c871450899e73d757d849 Mon Sep 17 00:00:00 2001 From: Shane Krueger Date: Sat, 20 Dec 2025 23:59:35 -0500 Subject: [PATCH 4/6] refactor: remove unused Microsoft.Extensions.Logging namespace --- other-benchmarks/dotnet-gql/server.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/other-benchmarks/dotnet-gql/server.cs b/other-benchmarks/dotnet-gql/server.cs index 834241ff..a79072fe 100644 --- a/other-benchmarks/dotnet-gql/server.cs +++ b/other-benchmarks/dotnet-gql/server.cs @@ -12,7 +12,6 @@ using Faker; using GraphQL; using GraphQL.Types; -using Microsoft.Extensions.Logging; var builder = WebApplication.CreateBuilder(args); From ac7d57a6670e58d8c95b60578a6bbe5f2e185842 Mon Sep 17 00:00:00 2001 From: Shane Krueger Date: Sun, 21 Dec 2025 00:34:34 -0500 Subject: [PATCH 5/6] refactor: remove graceful shutdown handling from server setup --- other-benchmarks/dotnet-gql/server.cs | 8 -------- 1 file changed, 8 deletions(-) diff --git a/other-benchmarks/dotnet-gql/server.cs b/other-benchmarks/dotnet-gql/server.cs index a79072fe..fd2ff6f8 100644 --- a/other-benchmarks/dotnet-gql/server.cs +++ b/other-benchmarks/dotnet-gql/server.cs @@ -31,14 +31,6 @@ var app = builder.Build(); -// Handle SIGINT/SIGTERM for graceful shutdown -var lifetime = app.Services.GetRequiredService(); -Console.CancelKeyPress += (sender, e) => -{ - e.Cancel = true; - lifetime.StopApplication(); -}; - // GraphQL endpoint app.UseGraphQL("/graphql"); app.UseGraphQLGraphiQL("/"); From 6d94f0a3f2029a60649947617d25d9d79ee3d1b7 Mon Sep 17 00:00:00 2001 From: Shane Krueger Date: Sun, 21 Dec 2025 00:37:03 -0500 Subject: [PATCH 6/6] refactor: update AuthorType to use ToMd5Hash method directly --- other-benchmarks/dotnet-gql/server.cs | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/other-benchmarks/dotnet-gql/server.cs b/other-benchmarks/dotnet-gql/server.cs index fd2ff6f8..0841c87c 100644 --- a/other-benchmarks/dotnet-gql/server.cs +++ b/other-benchmarks/dotnet-gql/server.cs @@ -57,7 +57,7 @@ public AuthorType() Field(x => x.Id); Field(x => x.Name); Field(x => x.Company); - Field("md5", x => x.Md5()); + Field("md5", x => x.Name.ToMd5Hash()); Field>("books") .Resolve(context => context.Source.Books); } @@ -83,10 +83,7 @@ public AppSchema(IServiceProvider serviceProvider) : base(serviceProvider) // Data Models public record Book(string Id, string Name, int NumPages); -public record Author(string Id, string Name, string Company, List Books) -{ - public string Md5() => Name.ToMd5Hash(); -} +public record Author(string Id, string Name, string Company, List Books); // MD5 Hashing Extension Method static class Md5Extensions