BlinkHost
BlinkHost
Lab·14 minute read·advanced

From the Measured systems series

Benchmarking JavaScript, Rust and Python Functions at the Edge

A reproducible lab for measuring cold, warm and concurrent function execution without turning unlike evidence into a misleading language leaderboard.

By BlinkHost Engineering · Published 01/09/2026

What you will understand

  • Design a comparable cross-runtime workload and label cache state honestly.
  • Collect distributions, failures, artifact identity and raw samples.
  • Run the no-dependency benchmark harness against endpoints you control.
Benchmarking JavaScript, Rust and Python functions at the edge

Benchmark charts are easy to make and surprisingly easy to make meaningless.

Put three runtimes on one graph, send a few requests, and one bar will be shorter. But was it a cold start? Did one artifact come from cache? Did the client reach the same region? Was Python executing the same work? Did a load generator wait for each response, masking requests delayed behind other requests?

This lab establishes a repeatable method before it offers a conclusion. You can use the downloadable harness against endpoints you control and keep the raw samples for review.

The question must be narrower than “which language is fastest?”

A useful first question is:

For the same bounded request-and-response workload, how do the deployed runtimes behave under cold, warm and concurrent execution from the same client location?

That definition fixes several variables:

  • identical response semantics;
  • no database or external network call in the baseline;
  • the same response size;
  • the same deployment region and policy class;
  • separately labelled cold and warm samples;
  • enough requests to report a distribution rather than one number.

It does not claim that a synthetic “hello” predicts a production application. It gives us a baseline that later experiments can extend.

What to measure

Collect at least five values:

  1. End-to-end latency as observed by the caller.
  2. Runtime duration measured at the execution boundary, if the platform exposes it.
  3. Artifact size of the verified deployable module.
  4. Failure rate, including timeout and non-2xx outcomes.
  5. Cache state or release state so cold and warm observations are not mixed.

Report p50, p95 and p99 instead of only the mean. The median describes a typical request. Tail percentiles reveal whether a smaller portion of users experiences a very different system.

Keep raw samples. A percentile without its sample count, time window and method is hard to evaluate.

Build equivalent handlers

The handler should return the same status, headers and body. For example:

{"message":"hello","version":1}

Avoid giving one runtime extra work through serialization, logging or framework middleware. A second experiment can deliberately compare a framework such as Hono with a direct handler, but it should carry a different label.

For the first run:

  • deploy each handler through its normal managed build;
  • record the source revision and artifact digest;
  • verify the response body before collecting timing;
  • warm each endpoint separately;
  • run endpoints in a rotating order to reduce time-of-day bias.

Cold and warm are operational states

“Cold start” is not a language property. It is the result of a platform's cache hierarchy, artifact loading, runtime initialization and current node state.

Define the condition you can actually create. For example:

  • evicted baseline: the platform confirms that the artifact is absent from the execution cache;
  • first observed request: the first request after a new release, without claiming eviction;
  • warm: a request after the same artifact has completed successfully on that node.

If you cannot control or observe eviction, call the result “first observed request,” not “cold start.” Precise labels are more valuable than dramatic ones.

Concurrency needs an arrival model

A loop that waits for one request before sending the next measures sequential latency. It does not measure behaviour under contention.

For a small lab, use bounded batches such as 1, 5 and 20 simultaneous requests and record each result. For serious load testing, use a tool capable of a constant arrival rate and account for coordinated omission—the measurement error that occurs when a slow system also slows the generation of new work.

Stay below production abuse thresholds unless you own the service and have explicitly approved the test. A benchmark is not permission to load-test somebody else's endpoint.

A truthful BlinkHost baseline

BlinkHost maintains different lifecycle labels for its function runtimes. Rust and TinyGo are generally available. JavaScript, TypeScript and Python are bounded function runtimes in public beta. Persistent application servers are a different workload and are not represented by this lab.

The current evidence is deliberately separated by what can be compared:

Runtime Artifact bytes Evidence boundary
Rust 39,280 Verified production artifact; four edge nodes executed successfully
TinyGo 920,636 Verified production artifact; four edge nodes executed successfully
JavaScript 961,879 Final production-engine candidate corpus
TypeScript 961,879 Final production-engine candidate corpus
Python 8,451,247 Verified production artifact; four edge nodes executed successfully

The JavaScript and TypeScript candidate corpus observed approximately 20.8 ms cold / 13.2 ms warm and 20.7 ms cold / 12.9 ms warm respectively on one development host. A Hono candidate observed approximately 60.3 ms cold / 54.5 ms warm. Those figures establish feasibility on that host. They are not combined with separately gathered Rust, TinyGo or Python production evidence to manufacture a cross-language ranking.

Artifact size is comparable here, but it is not a latency result. Python's packaged runtime is much larger because it includes a CPython-WASI environment; that fact suggests what to investigate, not what every request will experience.

Run the companion harness

The downloadable Node.js script accepts labelled URLs, verifies responses and writes JSON containing individual samples and percentiles.

node edge-function-benchmark.mjs \
  --requests 100 \
  --concurrency 5 \
  rust=https://your-rust-endpoint.example/hello \
  javascript=https://your-javascript-endpoint.example/hello \
  python=https://your-python-endpoint.example/hello

Use test endpoints that contain no customer data. The harness deliberately sends only GET requests and does not follow redirects.

Run at least three rounds from each intended geography. Preserve:

  • UTC start and end times;
  • client region and network;
  • release and artifact identity;
  • runtime lifecycle label;
  • raw result JSON;
  • errors and excluded samples, with reasons.

Interpret the result

Ask these questions in order:

  1. Did every runtime return the expected result?
  2. Were any samples lost or timed out?
  3. Are the tails stable across rounds?
  4. How large is the caller-network component compared with runtime duration?
  5. Does the workload fit the runtime's compatibility and lifecycle contract?
  6. Is the observed difference material to the user?

A 5 ms runtime difference may disappear inside 120 ms of network travel. A larger artifact may be irrelevant after cache warm-up. A slightly slower language may save weeks of development for the workload. Performance is a constraint to satisfy, not a universal language ranking.

Extend the lab carefully

After the baseline, add one variable at a time:

  • JSON parsing at fixed payload sizes;
  • a named database query using the same schema and row count;
  • one policy-controlled outbound request to the same target;
  • framework versus direct-handler overhead;
  • scheduled or background execution;
  • regional caller-to-edge latency.

Version the dataset whenever the runtime, compiler, edge fleet or handler changes. Historical numbers remain useful when readers can tell what changed.

Further reading

Versioned evidence

Artifact size baseline

Sizes are comparable; execution timings from different hosts and lifecycle tests are deliberately not combined into a false leaderboard.

Rust GA38.4 KiB

Production four-node execution

TinyGo GA899.1 KiB

Production four-node execution

JavaScript Public beta939.3 KiB

Production-engine candidate corpus

TypeScript Public beta939.3 KiB

Production-engine candidate corpus

Python Beta8253 KiB

Production four-node execution

Evidence snapshot: August 2026. A larger artifact is not automatically a slower application; initialization, workload, cache state, region and host policy must be measured separately.

Download the benchmark harness

A dependency-free Node.js script that records individual samples, failures and p50/p95/p99 values.

Download edge-function-benchmark.mjs

Inspect the evidence snapshot

Machine-readable artifact sizes, lifecycle labels and the separately scoped single-host observations used in this article.

Download edge-function-evidence-2026-08.json

Disclosure: BlinkHost artifact sizes and candidate timings are drawn from dated internal conformance evidence. Unlike execution environments are not combined into a cross-language ranking.

Related Field Notes

Benchmark JavaScript, Rust and Python Edge Functions | BlinkHost