Skip to content

Instantly share code, notes, and snippets.

@maacpiash
Last active September 28, 2023 04:04
Show Gist options
  • Save maacpiash/29a2c2d2a9caa5249ba3e73e4f49b974 to your computer and use it in GitHub Desktop.
Save maacpiash/29a2c2d2a9caa5249ba3e73e4f49b974 to your computer and use it in GitHub Desktop.
ASP.NET Core 8 RC 1 vs Bun 1.0.7 (Elysia) performance
using System.Text.Json.Serialization;
var builder = WebApplication.CreateSlimBuilder(args);
builder.Services.ConfigureHttpJsonOptions(options =>
{
options.SerializerOptions.TypeInfoResolverChain.Insert(0, AppJsonSerializerContext.Default);
});
var app = builder.Build();
var sampleTodos = new Todo[] {
new(1, "Walk the dog"),
new(2, "Do the dishes", DateOnly.FromDateTime(DateTime.Now)),
new(3, "Do the laundry", DateOnly.FromDateTime(DateTime.Now.AddDays(1))),
new(4, "Clean the bathroom"),
new(5, "Clean the car", DateOnly.FromDateTime(DateTime.Now.AddDays(2)))
};
var todosApi = app.MapGroup("/todos");
todosApi.MapGet("/", () => sampleTodos);
todosApi.MapGet("/{id}", (int id) =>
sampleTodos.FirstOrDefault(a => a.Id == id) is { } todo
? Results.Ok(todo)
: Results.NotFound());
app.Run();
public record Todo(int Id, string? Title, DateOnly? DueBy = null, bool IsComplete = false);
[JsonSerializable(typeof(Todo[]))]
internal partial class AppJsonSerializerContext : JsonSerializerContext
{
}
import { Elysia } from 'elysia'
const todos = [
{ id: 1, title: 'Walk the dog', dueBy: null, isComplete: false },
{ id: 2, title: 'Do the dishes', dueBy: '2023-09-27', isComplete: false },
{ id: 3, title: 'Do the laundry', dueBy: '2023-09-28', isComplete: false },
{ id: 4, title: 'Clean the bathroom', dueBy: null, isComplete: false },
{ id: 5, title: 'Clean the car', dueBy: '2023-09-29', isComplete: false },
]
const app = new Elysia().get('/todos', () => todos).listen(3000)

The tests were run using wrk. The command was to open 8 threads and 1000 connections to hit the server for 20 minutes.

ASP.NET Core 8.0.0.rc.1

For this test, there were two ASP.NET Core projects: webapi and webapiaot. The webapi project was published as a single-file, ready-to-run output. The webapiaot was also single-file, ready-to-run, but contained native instructions.

Common options

Both webapi and webapiaot projects had these options in the csproj files:

<PublishReadyToRun>true</PublishReadyToRun>
<PublishTrimmed>true</PublishTrimmed>
<SelfContained>true</SelfContained>
<IncludeNativeLibrariesForSelfExtract>true</IncludeNativeLibrariesForSelfExtract>

AOT (general)

AOT had this additional option:

<PublishAot>true</PublishAot>

Results:

Running 20m test @ http://localhost:5000/todos
  8 threads and 1000 connections
  Thread Stats   Avg      Stdev     Max   +/- Stdev
    Latency    10.30ms    2.62ms  71.82ms   74.24%
    Req/Sec    12.20k   645.42    26.51k    74.12%
  116549316 requests in 20.00m, 55.90GB read
Requests/sec:  97116.95
Transfer/sec:     47.70MB

AOT (size-opimised)

AOT (size-opimised) had these additional option:

<PublishAot>true</PublishAot>
<OptimizationPreference>Size</OptimizationPreference>
Running 20m test @ http://localhost:5000/todos
  8 threads and 1000 connections
  Thread Stats   Avg      Stdev     Max   +/- Stdev
    Latency    10.29ms    2.72ms  74.26ms   74.79%
    Req/Sec    12.21k   584.60    29.62k    73.77%
  116674061 requests in 20.00m, 55.96GB read
Requests/sec:  97220.68
Transfer/sec:     47.75MB

AOT (speed-opimised)

AOT (speed-opimised) had these additional option:

Results:

<PublishAot>true</PublishAot>
<OptimizationPreference>Speed</OptimizationPreference>

Results:

Running 20m test @ http://localhost:5000/todos
  8 threads and 1000 connections
  Thread Stats   Avg      Stdev     Max   +/- Stdev
    Latency     9.43ms    4.13ms  92.82ms   78.69%
    Req/Sec    13.38k     1.66k   35.40k    84.58%
  127838604 requests in 20.00m, 61.32GB read
Requests/sec: 106523.72
Transfer/sec:     52.32MB

Single-file deployment

In addition to the common options, the single-file deployment project also had these options. The PublishAot and OptimizationPreference options were not included in this csproj file.

<PublishSingleFile>true</PublishSingleFile>
<RuntimeIdentifier>linux-x64</RuntimeIdentifier>

Results:

Running 20m test @ http://localhost:5000/todos
  8 threads and 1000 connections
  Thread Stats   Avg      Stdev     Max   +/- Stdev
    Latency     9.41ms    2.35ms 180.00ms   77.45%
    Req/Sec    13.37k   652.28    23.40k    82.55%
  127684369 requests in 20.00m, 61.24GB read
Requests/sec: 106397.44
Transfer/sec:     52.26MB

Bun 1.0.7

Elysia

TypeScript: bun run index.ts

Running 20m test @ http://localhost:3000/todos
  8 threads and 1000 connections
  Thread Stats   Avg      Stdev     Max   +/- Stdev
    Latency    15.10ms  561.15us  43.84ms   74.85%
    Req/Sec     8.32k   428.45     9.86k    54.84%
  79455216 requests in 20.00m, 34.34GB read
Requests/sec:  66209.38
Transfer/sec:     29.30MB

JavaScript: bun run index.js

Running 20m test @ http://localhost:3000/todos
  8 threads and 1000 connections
  Thread Stats   Avg      Stdev     Max   +/- Stdev
    Latency    16.01ms  530.37us  47.02ms   75.10%
    Req/Sec     7.84k   403.69    11.04k    79.84%
  74919011 requests in 20.00m, 32.38GB read
Requests/sec:  62427.94
Transfer/sec:     27.62MB
var builder = WebApplication.CreateSlimBuilder(args);
var app = builder.Build();
var sampleTodos = new Todo[] {
new(1, "Walk the dog"),
new(2, "Do the dishes", DateOnly.FromDateTime(DateTime.Now)),
new(3, "Do the laundry", DateOnly.FromDateTime(DateTime.Now.AddDays(1))),
new(4, "Clean the bathroom"),
new(5, "Clean the car", DateOnly.FromDateTime(DateTime.Now.AddDays(2)))
};
var todosApi = app.MapGroup("/todos");
todosApi.MapGet("/", () => sampleTodos);
todosApi.MapGet("/{id}", (int id) =>
sampleTodos.FirstOrDefault(a => a.Id == id) is { } todo
? Results.Ok(todo)
: Results.NotFound());
app.Run();
public record Todo(int Id, string? Title, DateOnly? DueBy = null, bool IsComplete = false);
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment