Skip to content

Performance

Performance

go-ruby-net-http/net-http is the pure-Go library that rbgo can bind for Ruby’s Net::HTTP message codec. This page records a comparative, offline benchmark of that codec against the reference Ruby runtimes, part of the ecosystem-wide per-module parity suite.

Offline scope — this is the codec, not the network

net-http is a message codec with no I/O: DNS, the TCPSocket, the TLS handshake and the byte transfer are the host’s job (rbgo’s), by design. This benchmark therefore measures only the deterministic, network-free surface — building request bytes, parsing a response buffer (including chunked decode), URI form encoding, and header canonicalization. It is not an end-to-end HTTP latency comparison; the wire, the kernel and the TLS stack dominate any real request and are identical regardless of who builds the bytes.

What is measured

The same four network-free operations run through the pure-Go library (via its Go API) and through each reference runtime’s own Net::HTTP / URI. Inputs are built byte-for-byte identically on both sides, and — before any timing — every runtime emits a sha256 of each op’s output that is checked identical to the Go driver; a runtime that diverges is skipped, not timed. So each column is the same observable operation, apples-to-apples.

Opwhat it exercises
request-serializerequest line + MRI-ordered default headers + form body + Content-Length (Request.Bytes vs Net::HTTPGenericRequest#exec)
response-parsestatus line + multi-value headers + chunked Transfer-Encoding decode over a fixed buffer (ParseResponse vs read_new + reading_body)
form-encodeURI.encode_www_form over spaces / reserved bytes / % / multibyte UTF-8
header-canonicalizecanonicalize + join a 16-field header block (EachCapitalized vs each_capitalized)
  • Host: Apple M4 Max (Mac16,5, arm64), macOS 26.5.1 — date 2026-07-04.
  • Runtimes: Go 1.26.4 · MRI ruby 4.0.5 +PRISM · MRI + YJIT · JRuby 10.1.0.0 (OpenJDK 25) · TruffleRuby 34.0.1 (GraalVM CE Native).
  • Method: each process runs 5 untimed warm-up passes, then 60 timed passes of a fixed inner loop, timed with a monotonic clock; the best pass is reported as ns/op (lower is better). vs MRI < 1.00× means faster than MRI. Interpreter start-up is outside the timed region, so these are operation costs, not ruby file.rb process costs.

Summary — the pure-Go codec beats MRI and YJIT on all four

Opgo-ruby (ns/op)vs MRIvs YJITverdict
request-serialize1056.90.22×0.27×beats MRI + YJIT
response-parse1744.20.07×0.10×beats MRI + YJIT
form-encode1869.60.06×0.07×beats MRI + YJIT
header-canonicalize4120.00.28×0.34×beats MRI + YJIT

Unlike collection primitives (where MRI’s Hash / Array are hand-written C and hard to beat), Ruby’s Net::HTTP codec is pure Ruby built on regexps and per-line object allocation. Compiled Go with no interpreter dispatch and no per-header String/MatchData allocation is a structural win here, so there is no op in this offline surface where YJIT is faster — an honest result, not a cherry-pick: the numbers below are every op measured, and the cross-runtime CHECK gate guarantees each column computed the identical bytes.

Per-operation results (best of 60)

request-serialize

Runtimens/opvs MRI
go-ruby-net-http (pure Go)1056.90.22×
MRI4735.01.00×
MRI + YJIT3860.00.82×
JRuby3534.80.75×
TruffleRuby8707.91.84×

response-parse

Runtimens/opvs MRI
go-ruby-net-http (pure Go)1744.20.07×
MRI24400.01.00×
MRI + YJIT17035.00.70×
JRuby11425.60.47×
TruffleRuby17964.20.74×

form-encode

Runtimens/opvs MRI
go-ruby-net-http (pure Go)1869.60.06×
MRI29090.01.00×
MRI + YJIT25200.00.87×
JRuby12307.70.42×
TruffleRuby21444.20.74×

header-canonicalize

Runtimens/opvs MRI
go-ruby-net-http (pure Go)4120.00.28×
MRI14945.01.00×
MRI + YJIT12290.00.82×
JRuby5366.00.36×
TruffleRuby12570.60.84×

Reproduce

The harness is committed under benchmarks/: a self-contained Go driver (go/, which pins this library’s published pseudo-version via go.mod), the equivalent ruby/net_http.rb workload, and run.sh. Run OUTER=60 WARM=5 bash benchmarks/run.sh; env OUTER/WARM tune the pass budget and RUBY/JRUBY/TRUFFLERUBY select the runtime binaries. run.sh verifies every runtime’s sha256 CHECK against the Go driver before timing it.

Warm-up budget & noise — honest framing

Numbers reflect a fixed warm-process budget (5 warm-up + 60 timed passes in one process, best pass reported). The JVM/GraalVM JITs (JRuby, TruffleRuby) may need a larger warm-up to reach steady state, so their columns can understate peak throughput — TruffleRuby’s request-serialize (1.84×) is the clearest case of a cold, short-loop outlier. Every number here is a real measured value from the dated run above — nothing is fabricated, estimated, or cherry-picked. The go-ruby column is the pure-Go library; every other column is that interpreter’s own Net::HTTP / URI doing the equivalent work, proven to produce identical output.

What this does not claim

This measures the codec, not HTTP. A real request is dominated by DNS, the TCP round-trips and the TLS handshake — none of which this library performs, and all of which are identical no matter which runtime formats the bytes. The result to take away is narrow and honest: formatting and parsing HTTP/1.1 messages is materially cheaper in the pure-Go codec than in any Ruby runtime’s Net::HTTP, so binding it costs the interpreter nothing on the compute side.