Skip to content

Instantly share code, notes, and snippets.

@billywhizz
Last active April 7, 2024 20:22
Show Gist options
  • Save billywhizz/afbef853d07abed58d47cc257a65c586 to your computer and use it in GitHub Desktop.
Save billywhizz/afbef853d07abed58d47cc257a65c586 to your computer and use it in GitHub Desktop.
Crow v Bun HTTP Hello Shootout
build
*.json

Preparation

based on this blog post by daniel lemire

instructions from here

if you don't have conan installed

pip install conan

to build the Crow/Cpp server

conan profile detect --force
conan install . --output-folder=build --build=missing
cmake -B build -DCMAKE_TOOLCHAIN_FILE=conan_toolchain.cmake -DCMAKE_BUILD_TYPE=Release
cmake --build build

Running

to run the Crow/Cpp server, use the spawn.js script to launch a single instance (set env var CPUS=1) and monitor resource usage while it is running. By default, Crow spins up an internal thread pool with as many threads as there are cores on the system.

CPUS=1 bun spawn.js build/server

to run the bun server, use the spawn.js script to launch bun server.js and it will create a bun server.js process for every core on the system. Bun uses SO_REUSEPORT to allow multiple processes to listen on the same port and have the kernel load balance connections across the lustening processes

bun spawn.js bun server.js

run wrk

wrk -c 1024 -d 30 -t $(nproc --all) http://127.0.0.1:3000/simple

or use bombardier

bombardier -c 1024 -d 30s http://127.0.0.1:3000/simple

while the load generator is running, you should see the resource usage of the servers in the console of spawn.js. e.g. for Crow

children 1 user 263 system 240 cpu 503 rss 13762560
children 1 user 243 system 242 cpu 485 rss 13893632
children 1 user 249 system 234 cpu 483 rss 13893632
children 1 user 253 system 234 cpu 487 rss 13893632
children 1 user 237 system 254 cpu 491 rss 13893632
children 1 user 246 system 231 cpu 477 rss 13893632
children 1 user 253 system 237 cpu 490 rss 13893632
children 1 user 236 system 249 cpu 485 rss 13893632
children 1 user 256 system 228 cpu 484 rss 13893632
children 1 user 245 system 229 cpu 474 rss 13893632
children 1 user 249 system 238 cpu 487 rss 13893632
children 1 user 238 system 246 cpu 484 rss 13893632
children 1 user 236 system 247 cpu 483 rss 13893632
children 1 user 239 system 237 cpu 476 rss 13893632

the cpu metric tells us the Crow server is utilizing ~4.85 cores on average during the load test.

and for bun

children 8 user 229 system 250 cpu 479 rss 407080960
children 8 user 210 system 256 cpu 466 rss 407322624
children 8 user 204 system 268 cpu 472 rss 406839296
children 8 user 213 system 255 cpu 468 rss 406917120
children 8 user 210 system 257 cpu 467 rss 407650304
children 8 user 222 system 249 cpu 471 rss 408027136
children 8 user 220 system 252 cpu 472 rss 408014848
children 8 user 222 system 248 cpu 470 rss 407859200
children 8 user 231 system 239 cpu 470 rss 407310336
children 8 user 215 system 250 cpu 465 rss 404377600
children 8 user 214 system 256 cpu 470 rss 405102592
children 8 user 224 system 252 cpu 476 rss 405266432
children 8 user 220 system 254 cpu 474 rss 405196800
children 8 user 214 system 257 cpu 471 rss 404553728

the cpu metric tells us the Bun servers are utilizing ~4.7 cores on average during the load test.

if you set CPUS=1 for bun, you will be able to see that bun only uses a single thread for the event loop by default. cpu usage shows around 105% when running the load test. the extra cpu usage is likely the garbage collection/JIT threads in JavaScriptCore.

CPUS=1 bun spawn.js bun server.js
cmake_minimum_required(VERSION 3.15)
project(funserver CXX)
find_package(Crow REQUIRED)
add_executable(server server.cpp)
target_link_libraries(server Crow::Crow)
[requires]
crowcpp-crow/1.1.0
[generators]
CMakeDeps
CMakeToolchain
#include "crow.h"
int main()
{
crow::SimpleApp app;
app.loglevel(crow::LogLevel::Warning);
CROW_ROUTE(app, "/simple")([](){
return "Hello, World!";
});
app.port(3000).multithreaded().run();
//app.port(3000).concurrency(4).run();
}
Bun.serve({
fetch (req) {
const url = new URL(req.url);
if (url.pathname === "/simple") return new Response("Hello, World!");
return new Response("Not Found", { status: 404 });
},
port: Number(Bun.env.PORT || 3000),
reusePort: true,
address: Bun.env.ADDRESS || "127.0.0.1"
});
const fs = require('fs')
const os = require('os')
const cpus = parseInt(Bun.env.CPUS || os.availableParallelism(), 10)
let children = []
for (let i = 0; i < cpus; i++) {
const child = Bun.spawn(Bun.argv.slice(2), {
stdout: 'inherit',
onExit() {
children = children.filter(c => c !== child)
if (!children.length) clearTimeout(timer)
},
});
child.last_usr = 0
child.last_sys = 0
children.push(child)
}
const timer = setInterval(() => {
let total_usr = 0
let total_sys = 0
let total_mem = 0
for (const child of children) {
const stats = (new TextDecoder()).decode(fs.readFileSync(`/proc/${child.pid}/stat`)).split(' ')
const usr = Number(stats[13])
const sys = Number(stats[14])
const mem = Number(stats[23]) * 4096
const res = [usr - child.last_usr, sys - child.last_sys]
total_usr += res[0]
total_sys += res[1]
total_mem += mem
child.last_usr = usr
child.last_sys = sys
}
console.log(`children ${children.length} user ${total_usr} system ${total_sys} cpu ${total_usr + total_sys} rss ${total_mem}`)
}, 1000)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment