Campfire in F#

Basecamp's ONCE Campfire · ported to F# · Phase 7, live

F# just passed Rust.

Basecamp's chat app, ported to F# so it can't be told apart from the Rails original or the Rust port. Latest run: 1.32× Rust's throughput, +73.8% since tuning began.

1.32×of Rust, latest run
+73.8%since tuning began
16code changes to get here
24%ahead of Rust
Tue 6 Oct, 01:00 EDT · phase 5 · tier quickFirst whole-app run: the F# port boots and serves all five workloadscommit 72b938e · image (Docker port, wrk) build · overall 0.76× of Rust
16 connections1 connection
F#RustRailsF#/RustF#RustRailsF#/Rust
Room page9,130– µs11,285– µs2270.81×40× Rails1,665– µs2,487– µs1110.67×15× Rails
Messages page9,350– µs12,452– µs3900.75×24× Rails2,079– µs2,673– µs2090.78×10× Rails
Sidebar10,404– µs14,237– µs5810.73×18× Rails2,263– µs2,879– µs2680.79×8× Rails
Search4,321– µs5,650– µs4100.76×11× Rails1,722– µs2,095– µs1890.82×9× Rails
Post a message427– µs554– µs3100.77×1× Rails311– µs449– µs1600.69×2× Rails

What was done

Unit 5.3 ported the room, message, boost, bot API and search controllers, wired to the fragment cache and gzip-spliced page parts as in Rust. A quick wrk check, 6 s per path, through Docker's published port on the Mac (so it includes the port proxy and is not the benchmark method), F# then Rust, each on a fresh default seed. Rough, but the first time the real port faced Rust end to end.

Requests per second, CPU µs per request beneath. Rust was measured in the same run. Rails is the 5b baseline (3 reps), the last time it ran; it doesn't change. Greyed F# values were carried from an earlier run.
Tue 6 Oct, 03:16 EDT · baseline · tier 5bWhole-app baseline: bench/run, Rails vs Rust vs F#, 3 repscommit 794dc8b · image e111a4e (2 s warm-up) build · overall 0.79× of Rust
16 connections1 connection
F#RustRailsF#/RustF#RustRailsF#/Rust
Room page21,003– µs28,135– µs2270.75×92× Rails3,329– µs6,310– µs1110.53×30× Rails
Messages page23,097– µs29,406– µs3900.79×59× Rails4,044– µs6,625– µs2090.61×19× Rails
Sidebar19,115– µs27,938– µs5810.68×33× Rails3,738– µs6,116– µs2680.61×14× Rails
Search19,411– µs26,049– µs4100.75×47× Rails3,922– µs5,491– µs1890.71×21× Rails
Post a message6,186– µs6,082– µs3101.02×20× Rails1,909– µs2,727– µs1600.70×12× Rails

What was done

Plan step 5b, before any tuning. bench/run in the colima VM with the server pinned to CPUs 0-3 and the load generator to 4-7, host networking, 3 reps per app, every response checked (all 200), every post's row counted, decoded bytes identical to Rust's. A skeptical Opus review re-ran it and trimmed F#'s c=16 ratios slightly (room 0.71, messages 0.74) and flagged the 2 s warm-up, which the Phase 7 harness then lengthened. Falco measured at about 29 ns a request.

Requests per second, CPU µs per request beneath. Rust was measured in the same run. Rails is the 5b baseline (3 reps), the last time it ran; it doesn't change. Greyed F# values were carried from an earlier run.
Tue 6 Oct, 10:25 EDT · phase 7 start · tier 3Phase 7 starting point: the trustworthy harness, before any tuningcommit 5cc5ba4 · image, 3 reps build · overall 0.76× of Rust
16 connections1 connection
F#RustRailsF#/RustF#RustRailsF#/Rust
Room page21,892173 µs28,720122 µs2270.76×96× Rails3,885453 µs6,500142 µs1110.60×35× Rails
Messages page23,346161 µs30,230114 µs3900.77×60× Rails4,227449 µs6,981136 µs2090.61×20× Rails
Sidebar18,888200 µs28,378123 µs5810.67×32× Rails3,837443 µs6,366146 µs2680.60×14× Rails
Search20,026184 µs26,606124 µs4100.75×49× Rails3,990468 µs5,638158 µs1890.71×21× Rails
Post a message6,046549 µs7,140389 µs3100.85×19× Rails1,958998 µs2,814399 µs1600.70×12× Rails

What was done

Unit 7.0 rebuilt the measuring before anyone optimized: container logs capped, every route warmed for at least 30 s until flat within 3%, the host's pinned CPUs probed before each window, CPU per request from the container's cgroup, and bench/quick for Rust and F# only. Rust was measured here once more, and these Rust medians are the stored reference every later dot compares against.

Requests per second, CPU µs per request beneath. Rust is the stored Phase 7 starting point. Rails is the 5b baseline (3 reps), the last time it ran; it doesn't change. Greyed F# values were carried from an earlier run.
Tue 6 Oct, 11:58 EDT · database · tier 2statements through SQLitePCLRaw (prepare once, bind by index, columns by ordinal) instead of Microsoft.Data.Sqlite commandscommit 5d2a8aa · mounted build · overall 0.81× of Rust
16 connections1 connection
F#RustRailsF#/RustF#RustRailsF#/Rust
Room page21,892173 µs28,720122 µs2270.76×96× Rails3,885453 µs6,500142 µs1110.60×35× Rails
Messages page23,346161 µs30,230114 µs3900.77×60× Rails4,227449 µs6,981136 µs2090.61×20× Rails
Sidebar23,107162 µs28,378123 µs5810.81×40× Rails3,909449 µs6,366146 µs2680.61×15× Rails
Search23,082159 µs26,606124 µs4100.87×56× Rails4,158454 µs5,638158 µs1890.74×22× Rails
Post a message6,046549 µs7,140389 µs3100.85×19× Rails1,958998 µs2,814399 µs1600.70×12× Rails

What was done

  • 5d2a8aa Db: run statements through SQLitePCLRaw instead of Microsoft.Data.Sqlite commands
Requests per second, CPU µs per request beneath. Rust is the stored Phase 7 starting point. Rails is the 5b baseline (3 reps), the last time it ran; it doesn't change. Greyed F# values were carried from an earlier run.
Tue 6 Oct, 12:18 EDT · database · tier 2no per-connection or memstatus mutex, direct C-API calls for rows and binds, timestamps parsed from UTF-8 bytescommit b7f020f · mounted build · overall 0.94× of Rust
16 connections1 connection
F#RustRailsF#/RustF#RustRailsF#/Rust
Room page25,851141 µs28,720122 µs2270.90×114× Rails3,885453 µs6,500142 µs1110.60×35× Rails
Messages page28,704126 µs30,230114 µs3900.95×74× Rails4,227449 µs6,981136 µs2090.61×20× Rails
Sidebar24,993151 µs28,378123 µs5810.88×43× Rails3,909449 µs6,366146 µs2680.61×15× Rails
Search26,034146 µs26,606124 µs4100.98×63× Rails4,158454 µs5,638158 µs1890.74×22× Rails
Post a message6,940467 µs7,140389 µs3100.97×22× Rails1,933990 µs2,814399 µs1600.69×12× Rails

What was done

  • 286c2ca Db: SQLite without the per-connection and memory-statistics mutexes
  • b7f020f Db: call the hot SQLite functions directly, and read timestamps from the bytes SQLite holds
Requests per second, CPU µs per request beneath. Rust is the stored Phase 7 starting point. Rails is the 5b baseline (3 reps), the last time it ran; it doesn't change. Greyed F# values were carried from an earlier run.
Tue 6 Oct, 12:24 EDT · database · tier 2a read that found a free connection returns without Task.Yield (no cross-thread hand-off per read)commit 15b1376 · mounted build · overall 0.96× of Rust
16 connections1 connection
F#RustRailsF#/RustF#RustRailsF#/Rust
Room page27,240141 µs28,720122 µs2270.95×120× Rails5,405264 µs6,500142 µs1110.83×49× Rails
Messages page28,704126 µs30,230114 µs3900.95×74× Rails4,227449 µs6,981136 µs2090.61×20× Rails
Sidebar26,888143 µs28,378123 µs5810.95×46× Rails5,175275 µs6,366146 µs2680.81×19× Rails
Search26,034146 µs26,606124 µs4100.98×63× Rails4,158454 µs5,638158 µs1890.74×22× Rails
Post a message6,940467 µs7,140389 µs3100.97×22× Rails1,933990 µs2,814399 µs1600.69×12× Rails

What was done

  • 15b1376 Db: a read that found a free connection returns its result without yielding (experiment)
Requests per second, CPU µs per request beneath. Rust is the stored Phase 7 starting point. Rails is the 5b baseline (3 reps), the last time it ran; it doesn't change. Greyed F# values were carried from an earlier run.
Tue 6 Oct, 13:17 EDT · database · tier 3unit 7.1 database path: image rebuilt from HEAD, five workloads at c=1 and c=16, 3 repscommit df6ffd9 · sha256:5b3be84342fe build · overall 1.00× of Rust
16 connections1 connection
F#RustRailsF#/RustF#RustRailsF#/Rust
Room page28,328136 µs28,720122 µs2270.99×125× Rails5,589258 µs6,500142 µs1110.86×50× Rails
Messages page31,066124 µs30,230114 µs3901.03×80× Rails6,096243 µs6,981136 µs2090.87×29× Rails
Sidebar27,356142 µs28,378123 µs5810.96×47× Rails5,177278 µs6,366146 µs2680.81×19× Rails
Search27,801134 µs26,606124 µs4101.04×68× Rails4,855364 µs5,638158 µs1890.86×26× Rails
Post a message7,004452 µs7,140389 µs3100.98×23× Rails2,328783 µs2,814399 µs1600.83×15× Rails

What was done

  • c9964b3 Db: find a cached statement by a hash of its length and sixteen characters, and build the row list without an enumerator
  • d7ce54c Db: say why a read no longer yields
Requests per second, CPU µs per request beneath. Rust is the stored Phase 7 starting point. Rails is the 5b baseline (3 reps), the last time it ran; it doesn't change. Greyed F# values were carried from an earlier run.
Tue 6 Oct, 15:18 EDT · runtime · tier 2allocation cuts: gzip written into the pipe, to_i fast path, key buffer reuse, assetPath and preload-link memo (room 146 to 113 KB per request)commit 2e0f663 · mounted build · overall 0.98× of Rust
16 connections1 connection
F#RustRailsF#/RustF#RustRailsF#/Rust
Room page28,701117 µs28,720122 µs2271.00×126× Rails5,589258 µs6,500142 µs1110.86×50× Rails
Messages page30,267110 µs30,230114 µs3901.00×78× Rails6,096243 µs6,981136 µs2090.87×29× Rails
Sidebar27,012129 µs28,378123 µs5810.95×46× Rails5,177278 µs6,366146 µs2680.81×19× Rails
Search25,864129 µs26,606124 µs4100.97×63× Rails4,855364 µs5,638158 µs1890.86×26× Rails
Post a message7,004452 µs7,140389 µs3100.98×23× Rails2,328783 µs2,814399 µs1600.83×15× Rails

What was done

  • 2e0f663 Cut per-request allocation on the page paths (146 to 113 KB room, 121 to 82 KB messages)
Requests per second, CPU µs per request beneath. Rust is the stored Phase 7 starting point. Rails is the 5b baseline (3 reps), the last time it ran; it doesn't change. Greyed F# values were carried from an earlier run.
Tue 6 Oct, 16:11 EDT · runtime · tier 2A/B: default spin limit on the new image, run Bcommit 0e564f3 · sha256:9e87cfde74c3 build · overall 1.03× of Rust
16 connections1 connection
F#RustRailsF#/RustF#RustRailsF#/Rust
Room page28,701117 µs28,720122 µs2271.00×126× Rails5,589258 µs6,500142 µs1110.86×50× Rails
Messages page33,771113 µs30,230114 µs3901.12×87× Rails6,096243 µs6,981136 µs2090.87×29× Rails
Sidebar27,012129 µs28,378123 µs5810.95×46× Rails5,177278 µs6,366146 µs2680.81×19× Rails
Search29,233131 µs26,606124 µs4101.10×71× Rails4,855364 µs5,638158 µs1890.86×26× Rails
Post a message7,004452 µs7,140389 µs3100.98×23× Rails2,328783 µs2,814399 µs1600.83×15× Rails

What was done

  • dcb8032 Ship the runtime settings of unit 7.2: no thread-pool spinning, a 64 MiB gen0 budget; hoist sanitizeTags' allowlist
Requests per second, CPU µs per request beneath. Rust is the stored Phase 7 starting point. Rails is the 5b baseline (3 reps), the last time it ran; it doesn't change. Greyed F# values were carried from an earlier run.
Tue 6 Oct, 16:50 EDT · runtime · tier 3unit 7.2 runtime: image rebuilt from HEAD with the 64MB gen0 budget and the allocation cuts, five workloads at c=1 and c=16, 3 repscommit 1d0f3b2 · sha256:e316ad01e8ed build · overall 1.05× of Rust
16 connections1 connection
F#RustRailsF#/RustF#RustRailsF#/Rust
Room page30,672124 µs28,720122 µs2271.07×135× Rails5,654252 µs6,500142 µs1110.87×51× Rails
Messages page33,229116 µs30,230114 µs3901.10×85× Rails6,293229 µs6,981136 µs2090.90×30× Rails
Sidebar28,192135 µs28,378123 µs5810.99×48× Rails5,336266 µs6,366146 µs2680.84×20× Rails
Search28,713128 µs26,606124 µs4101.08×70× Rails4,695375 µs5,638158 µs1890.83×25× Rails
Post a message7,186441 µs7,140389 µs3101.01×23× Rails2,275809 µs2,814399 µs1600.81×14× Rails

What was done

  • b31452b Do not ship DOTNET_ThreadPool_UnfairSemaphoreSpinLimit=0: it costs messages and search 10-14% at c=16
  • 1d0f3b2 Dockerfile: say only what was measured about the gen0 budget
Requests per second, CPU µs per request beneath. Rust is the stored Phase 7 starting point. Rails is the 5b baseline (3 reps), the last time it ran; it doesn't change. Greyed F# values were carried from an earlier run.
Tue 6 Oct, 17:54 EDT · crypto-logging · tier 2keyed HMAC and AES state per thread, hand parsers for the expiry and the cookie envelope, remembered avatar tokens and stream names (crypto half, c=16)commit a696844 · mounted build · overall 1.11× of Rust
16 connections1 connection
F#RustRailsF#/RustF#RustRailsF#/Rust
Room page33,436112 µs28,720122 µs2271.16×147× Rails5,654252 µs6,500142 µs1110.87×51× Rails
Messages page33,229116 µs30,230114 µs3901.10×85× Rails6,293229 µs6,981136 µs2090.90×30× Rails
Sidebar33,614114 µs28,378123 µs5811.18×58× Rails5,336266 µs6,366146 µs2680.84×20× Rails
Search29,413129 µs26,606124 µs4101.11×72× Rails4,695375 µs5,638158 µs1890.83×25× Rails
Post a message7,186441 µs7,140389 µs3101.01×23× Rails2,275809 µs2,814399 µs1600.81×14× Rails

What was done

  • af0ced4 Stamp each logged run with the commit its build came from
  • a696844 Cache keyed HMAC and AES state per thread, parse envelopes without the regex or JSON parser, remember avatar tokens
Requests per second, CPU µs per request beneath. Rust is the stored Phase 7 starting point. Rails is the 5b baseline (3 reps), the last time it ran; it doesn't change. Greyed F# values were carried from an earlier run.
Tue 6 Oct, 17:55 EDT · crypto-logging · tier 2both halves at one connection (c=1)commit 0b67670 · mounted build · overall 1.20× of Rust
16 connections1 connection
F#RustRailsF#/RustF#RustRailsF#/Rust
Room page39,42496 µs28,720122 µs2271.37×174× Rails7,405213 µs6,500142 µs1111.14×67× Rails
Messages page33,229116 µs30,230114 µs3901.10×85× Rails6,293229 µs6,981136 µs2090.90×30× Rails
Sidebar38,35999 µs28,378123 µs5811.35×66× Rails7,612192 µs6,366146 µs2681.20×28× Rails
Search32,737114 µs26,606124 µs4101.23×80× Rails5,820327 µs5,638158 µs1891.03×31× Rails
Post a message7,186441 µs7,140389 µs3101.01×23× Rails2,275809 µs2,814399 µs1600.81×14× Rails

What was done

  • 0b67670 Write the request log line as UTF-8 into a batch instead of through ILogger
Requests per second, CPU µs per request beneath. Rust is the stored Phase 7 starting point. Rails is the 5b baseline (3 reps), the last time it ran; it doesn't change. Greyed F# values were carried from an earlier run.
Tue 6 Oct, 18:42 EDT · crypto-logging · tier 3unit 7.3 crypto-logging: image rebuilt from HEAD with keyed HMAC/AES state, hand parsers, token memo and the batched request log, five workloads at c=1 and c=16, 3 repscommit b1d55bf · sha256:53ce6520c2c2 build · overall 1.26× of Rust
16 connections1 connection
F#RustRailsF#/RustF#RustRailsF#/Rust
Room page38,60698 µs28,720122 µs2271.34×170× Rails7,524210 µs6,500142 µs1111.16×68× Rails
Messages page37,933102 µs30,230114 µs3901.25×97× Rails8,112200 µs6,981136 µs2091.16×39× Rails
Sidebar36,659104 µs28,378123 µs5811.29×63× Rails7,690188 µs6,366146 µs2681.21×29× Rails
Search33,798108 µs26,606124 µs4101.27×82× Rails5,837322 µs5,638158 µs1891.04×31× Rails
Post a message8,108396 µs7,140389 µs3101.14×26× Rails2,727697 µs2,814399 µs1600.97×17× Rails

What was done

No code change since the previous dot (a measurement only).

Requests per second, CPU µs per request beneath. Rust is the stored Phase 7 starting point. Rails is the 5b baseline (3 reps), the last time it ran; it doesn't change. Greyed F# values were carried from an earlier run.
Tue 6 Oct, 18:51 EDT · post · tier 2default host no longer watches the content root for appsettings changes (File Watch thread and lstat), post c=16 and c=1, 1 repcommit bc80d28 · mounted build · overall 1.28× of Rust
16 connections1 connection
F#RustRailsF#/RustF#RustRailsF#/Rust
Room page38,60698 µs28,720122 µs2271.34×170× Rails7,524210 µs6,500142 µs1111.16×68× Rails
Messages page37,933102 µs30,230114 µs3901.25×97× Rails8,112200 µs6,981136 µs2091.16×39× Rails
Sidebar36,659104 µs28,378123 µs5811.29×63× Rails7,690188 µs6,366146 µs2681.21×29× Rails
Search33,798108 µs26,606124 µs4101.27×82× Rails5,837322 µs5,638158 µs1891.04×31× Rails
Post a message8,796358 µs7,140389 µs3101.23×28× Rails2,949647 µs2,814399 µs1601.05×18× Rails

What was done

  • bc80d28 Front: do not watch the content root for appsettings changes
Requests per second, CPU µs per request beneath. Rust is the stored Phase 7 starting point. Rails is the 5b baseline (3 reps), the last time it ran; it doesn't change. Greyed F# values were carried from an earlier run.
Tue 6 Oct, 19:14 EDT · post · tier 2 · revertedgzip a post answer (9 KB of HTML) and splice pieces through the zlib shim with a window that fits, no dictionary for a short tail: no gain beyond noise, revertedcommit 5c23899 · mounted build · overall 1.28× of Rust
16 connections1 connection
F#RustRailsF#/RustF#RustRailsF#/Rust
Room page38,60698 µs28,720122 µs2271.34×170× Rails7,524210 µs6,500142 µs1111.16×68× Rails
Messages page37,933102 µs30,230114 µs3901.25×97× Rails8,112200 µs6,981136 µs2091.16×39× Rails
Sidebar36,659104 µs28,378123 µs5811.29×63× Rails7,690188 µs6,366146 µs2681.21×29× Rails
Search33,798108 µs26,606124 µs4101.27×82× Rails5,837322 µs5,638158 µs1891.04×31× Rails
Post a message8,852356 µs7,140389 µs3101.24×29× Rails2,985644 µs2,814399 µs1601.06×19× Rails

What was done

  • 5c23899 Deflate small gzip bodies and splice pieces through the zlib shim; no dictionary for a short tail after a fragment
Requests per second, CPU µs per request beneath. Rust is the stored Phase 7 starting point. Rails is the 5b baseline (3 reps), the last time it ran; it doesn't change. Greyed F# values were carried from an earlier run.
Tue 6 Oct, 19:17 EDT · post · tier 2control for the identity body work: the 416 KB room page with Accept-Encoding: identity at c=16 on the unchanged build (identity numbers are logged under room_show_identity; the chart does not show them)commit 5bb1dd5 · mounted build · overall 1.28× of Rust
16 connections1 connection
F#RustRailsF#/RustF#RustRailsF#/Rust
Room page38,60698 µs28,720122 µs2271.34×170× Rails7,524210 µs6,500142 µs1111.16×68× Rails
Messages page37,933102 µs30,230114 µs3901.25×97× Rails8,112200 µs6,981136 µs2091.16×39× Rails
Sidebar36,659104 µs28,378123 µs5811.29×63× Rails7,690188 µs6,366146 µs2681.21×29× Rails
Search33,798108 µs26,606124 µs4101.27×82× Rails5,837322 µs5,638158 µs1891.04×31× Rails
Post a message8,796358 µs7,140389 µs3101.23×28× Rails2,949647 µs2,814399 µs1601.05×18× Rails

What was done

  • 5c23899 Deflate small gzip bodies and splice pieces through the zlib shim; no dictionary for a short tail after a fragment
  • f18616d Revert "Deflate small gzip bodies and splice pieces through the zlib shim"
Requests per second, CPU µs per request beneath. Rust is the stored Phase 7 starting point. Rails is the 5b baseline (3 reps), the last time it ran; it doesn't change. Greyed F# values were carried from an earlier run.
Tue 6 Oct, 19:40 EDT · post · tier 2A/B in one build: Kestrel's own 4 KB blocks (CAMPFIRE_NO_CONN_POOL, mean of 2), identity room page c=16commit dcb5ffa · mounted build · overall 1.28× of Rust
16 connections1 connection
F#RustRailsF#/RustF#RustRailsF#/Rust
Room page38,60698 µs28,720122 µs2271.34×170× Rails7,524210 µs6,500142 µs1111.16×68× Rails
Messages page37,933102 µs30,230114 µs3901.25×97× Rails8,112200 µs6,981136 µs2091.16×39× Rails
Sidebar36,659104 µs28,378123 µs5811.29×63× Rails7,690188 µs6,366146 µs2681.21×29× Rails
Search33,798108 µs26,606124 µs4101.27×82× Rails5,837322 µs5,638158 µs1891.04×31× Rails
Post a message8,796358 µs7,140389 µs3101.23×28× Rails2,949647 µs2,814399 µs1601.05×18× Rails

What was done

  • dcb5ffa Kestrel connection pipes get 64 KB segments for large bodies
Requests per second, CPU µs per request beneath. Rust is the stored Phase 7 starting point. Rails is the 5b baseline (3 reps), the last time it ran; it doesn't change. Greyed F# values were carried from an earlier run.
Tue 6 Oct, 20:02 EDT · post · tier 2control for the post path A/B: file watching off, nothing else (mean of 3 alternating runs, c=16)commit 3b88200 · mounted build · overall 1.28× of Rust
16 connections1 connection
F#RustRailsF#/RustF#RustRailsF#/Rust
Room page38,60698 µs28,720122 µs2271.34×170× Rails7,524210 µs6,500142 µs1111.16×68× Rails
Messages page37,933102 µs30,230114 µs3901.25×97× Rails8,112200 µs6,981136 µs2091.16×39× Rails
Sidebar36,659104 µs28,378123 µs5811.29×63× Rails7,690188 µs6,366146 µs2681.21×29× Rails
Search33,798108 µs26,606124 µs4101.27×82× Rails5,837322 µs5,638158 µs1891.04×31× Rails
Post a message8,747357 µs7,140389 µs3101.22×28× Rails2,949647 µs2,814399 µs1601.05×18× Rails

What was done

  • f849119 Revert "Kestrel connection pipes get 64 KB segments for large bodies"
Requests per second, CPU µs per request beneath. Rust is the stored Phase 7 starting point. Rails is the 5b baseline (3 reps), the last time it ran; it doesn't change. Greyed F# values were carried from an earlier run.
Tue 6 Oct, 20:02 EDT · post · tier 2message partial broadcast as UTF-8 (no string), unread ping encoded once, no body parse for mentions when there is no attachment (mean of 3 alternating runs, c=16; allocation 183.6 to 154.1 KB per post)commit 571d25a · mounted build · overall 1.28× of Rust
16 connections1 connection
F#RustRailsF#/RustF#RustRailsF#/Rust
Room page38,60698 µs28,720122 µs2271.34×170× Rails7,524210 µs6,500142 µs1111.16×68× Rails
Messages page37,933102 µs30,230114 µs3901.25×97× Rails8,112200 µs6,981136 µs2091.16×39× Rails
Sidebar36,659104 µs28,378123 µs5811.29×63× Rails7,690188 µs6,366146 µs2681.21×29× Rails
Search33,798108 µs26,606124 µs4101.27×82× Rails5,837322 µs5,638158 µs1891.04×31× Rails
Post a message9,034349 µs7,140389 µs3101.27×29× Rails2,949647 µs2,814399 µs1601.05×18× Rails

What was done

  • 571d25a Post path: broadcast the message partial as UTF-8, encode the unread ping once, skip parsing bodies with no attachments for mentions
Requests per second, CPU µs per request beneath. Rust is the stored Phase 7 starting point. Rails is the 5b baseline (3 reps), the last time it ran; it doesn't change. Greyed F# values were carried from an earlier run.
Tue 6 Oct, 20:49 EDT · post · tier 3unit 7.4 post and remaining paths: image rebuilt from HEAD with file watching off and the post-path cuts (UTF-8 message broadcast, one unread ping encoding, no mention parse without attachments), five workloads at c=1 and c=16, 3 repscommit 3e321ac · sha256:052d89880648 build · overall 1.28× of Rust
16 connections1 connection
F#RustRailsF#/RustF#RustRailsF#/Rust
Room page37,726101 µs28,720122 µs2271.31×166× Rails7,462211 µs6,500142 µs1111.15×67× Rails
Messages page38,174100 µs30,230114 µs3901.26×98× Rails8,238199 µs6,981136 µs2091.18×39× Rails
Sidebar37,900101 µs28,378123 µs5811.34×65× Rails7,822186 µs6,366146 µs2681.23×29× Rails
Search33,565109 µs26,606124 µs4101.26×82× Rails5,814325 µs5,638158 µs1891.03×31× Rails
Post a message8,892353 µs7,140389 µs3101.25×29× Rails3,060631 µs2,814399 µs1601.09×19× Rails

What was done

No code change since the previous dot (a measurement only).

Requests per second, CPU µs per request beneath. Rust is the stored Phase 7 starting point. Rails is the 5b baseline (3 reps), the last time it ran; it doesn't change. Greyed F# values were carried from an earlier run.
Tue 6 Oct, 21:52 EDT · post · tier 2verifier fixes: long binds keep the GC transition, mention parse skipped only for bodies of <=800 units, console never coloured (mean of 2 alternating runs; control below)commit 69dc486 · mounted build · overall 1.28× of Rust
16 connections1 connection
F#RustRailsF#/RustF#RustRailsF#/Rust
Room page37,726101 µs28,720122 µs2271.31×166× Rails7,462211 µs6,500142 µs1111.15×67× Rails
Messages page38,174100 µs30,230114 µs3901.26×98× Rails8,238199 µs6,981136 µs2091.18×39× Rails
Sidebar37,900101 µs28,378123 µs5811.34×65× Rails7,822186 µs6,366146 µs2681.23×29× Rails
Search34,017108 µs26,606124 µs4101.28×83× Rails5,906322 µs5,638158 µs1891.05×31× Rails
Post a message8,784354 µs7,140389 µs3101.23×28× Rails3,006637 µs2,814399 µs1601.07×19× Rails

What was done

  • 69dc486 Phase 7 verifier fixes: no console colour, mention parse skipped only for bodies that cannot fail, long binds keep the GC transition
Requests per second, CPU µs per request beneath. Rust is the stored Phase 7 starting point. Rails is the 5b baseline (3 reps), the last time it ran; it doesn't change. Greyed F# values were carried from an earlier run.
Wed 7 Oct, 02:37 EDT · final · tier 3Phase 7 area-end re-baseline: bench/run --apps reference,rust,fsharp --reps 3, all suites, c=1/16/64, image 82ebcbc734ec from c3e0eca; Rust re-measured (within 1% of the stored baseline). Room page c=16 median 40,422 is ~6% above steady state: 2 of 3 reps settled before the thread pool grew; 90-150 s runs read ~38,000 (100 us/req)commit c3e0eca · sha256:82ebcbc734ec build · overall 1.30× of Rust
16 connections1 connection
F#RustRailsF#/RustF#RustRailsF#/Rust
Room page40,42294 µs28,720122 µs2271.41×178× Rails7,617209 µs6,500142 µs1111.17×69× Rails
Messages page37,836102 µs30,230114 µs3901.25×97× Rails8,312199 µs6,981136 µs2091.19×40× Rails
Sidebar37,555102 µs28,378123 µs5811.32×65× Rails8,000181 µs6,366146 µs2681.26×30× Rails
Search34,070107 µs26,606124 µs4101.28×83× Rails6,055315 µs5,638158 µs1891.07×32× Rails
Post a message8,905353 µs7,140389 µs3101.25×29× Rails3,052629 µs2,814399 µs1601.08×19× Rails

What was done

No code change since the previous dot (a measurement only).

Requests per second, CPU µs per request beneath. Rust is the stored Phase 7 starting point. Rails is the 5b baseline (3 reps), the last time it ran; it doesn't change. Greyed F# values were carried from an earlier run.
Wed 7 Oct, 04:46 EDT · threadpool · tier 3DOTNET_HillClimbing_Disable=1 in the image (59ee9ae), 3 reps, F# onlycommit 2e6c08e · sha256:421f335c3ff8 build · overall 1.32× of Rust
16 connections1 connection
F#RustRailsF#/RustF#RustRailsF#/Rust
Room page39,14491 µs28,720122 µs2271.36×172× Rails7,391209 µs6,500142 µs1111.14×67× Rails
Messages page42,14484 µs30,230114 µs3901.39×108× Rails8,115202 µs6,981136 µs2091.16×39× Rails
Sidebar37,81495 µs28,378123 µs5811.33×65× Rails7,752185 µs6,366146 µs2681.22×29× Rails
Search34,002108 µs26,606124 µs4101.28×83× Rails5,841326 µs5,638158 µs1891.04×31× Rails
Post a message8,738358 µs7,140389 µs3101.22×28× Rails2,976645 µs2,814399 µs1601.06×19× Rails

What was done

  • 59ee9ae Unit 7.5: disable thread-pool hill climbing in the image
Requests per second, CPU µs per request beneath. Rust is the stored Phase 7 starting point. Rails is the 5b baseline (3 reps), the last time it ran; it doesn't change. Greyed F# values were carried from an earlier run.
The climb. F#'s throughput as a share of Rust's, one dot per measured run (geometric mean of the five workloads, 16 connections). Hover, tap or tab to a dot for that run's numbers and what changed. Hollow grey dots were tried and reverted. Latest: Wed 7 Oct, 04:46 EDT.How to read it

(btw, Muse sucks) How this started, and why none of its numbers are on the chart.

The All Talk room, signed in as David: the page Rails, Rust and F# each render to these same pixels.
Same pixelsOne screenshot, because the three are one: Rails', Rust's and F#'s captures of this page are byte-identical.

Same pixels, different engines · the room page

Same software.

  1. Railsslowthe original

    239requests a secondserved while you watch, at 239 a second

  2. Rustfast120× Rails

    28,623requests a secondserved while you watch, at 28,623 a second

  3. F#faster1.41× Rust · 169× Rails

    40,422requests a secondserved while you watch, at 40,422 a second

Each counter ticks at that engine's measured rate for this page, signed in, at 16 connections.How we know

22,374of 22,374

Checked against the originals

Byte for byte.

Every view render identical to the Rust port's. Rich text, cookies, the HTTP kit and thumbnails matched too.

  1. SecondsTier 1: In-process A/B micro-benchmarks and the affected tests; SageFs hot reload and live tests optional. Decides: which variant to try.
  2. MinutesTier 2: dotnet publish, mounted into the existing container (no image rebuild), F#-only quick bench against the stored Rust numbers. Decides: whether we're happy with the area.
  3. About an hourTier 3: Rebuild the image; bench/run with Rust and Rails; bin/verify, the differentials and a parity smoke. Decides: the number that gets reported.

A change of process · 6 Oct 08:13

Rust became a fixed target.

F# measured alone against stored Rust numbers, in loops of seconds, minutes and an hour. That's why the dots come fast.

The successor

Check out the onCrack version

The same Campfire, pixel for pixel: 944 of 944 screenshots equal to Rails. onCrack was forked from this port, with the "same work as Rust" constraint dropped: caches, group commit, and a Cable layer that keeps every client.

The onCrack repo The onCrack page

Cable fan-out against Rust at 100 / 500 / 1,000 / 2,000 clients: 1.65× / 1.24× / 1.17× / 1.01× (level at 2,000; source) · The final report