A microsecond is one-millionth of a second. Light travels less than 300 meters in that time. A human blink lasts hundreds of thousands of them.
In most businesses, saving one would be meaningless. In electronic trading, firms design networks, rent space beside exchanges and write specialized software to save a handful. Nasdaq says its co-location network delivers round-trip order acknowledgements in less than 50 microseconds, and it sells upgrades that shave off another two to five.
The numbers sound absurd until you consider what a trading system does with them. It receives a market-data message, updates its view of prices, evaluates a strategy, checks risk limits, builds an order and sends it back. If the opportunity exists because someone else's quote has gone stale, the first correct response captures it. The second gets a rejection or a worse price.
Which is why a programmer at a trading firm cares about things that barely register in ordinary software. Where a piece of memory sits. Whether a task pauses. How many instructions it takes to update an order book.
The code is not merely supporting the trading business. At this timescale, the code is part of the trading strategy.
What happens before an order moves?
An exchange broadcasts a stream of events. A new buy order appears. A sell order is cancelled. A trade removes part of the quantity available at the best price. From those messages a trading firm rebuilds the order book, the live map of who will buy and sell at each price. Its copy of that map must be correct before any strategy can act on it.
The operations are simple: add an order, cancel one, change its quantity, match a buyer to a seller, report the best prices. The difficulty is repetition. A system must process an enormous stream while preserving exact price and time priority. One corrupted update produces a bad order. One unexpected pause turns a sensible order into a stale one.
So trading engineers watch two different kinds of speed. Throughput is how many messages a system handles over time. Latency is how long a single message takes. A system can average excellent throughput and still stall now and then. For a latency-sensitive strategy, the slowest one per cent — or the slowest hundredth of one per cent — matters more than the average.
Why researchers love Python
Python dominates quantitative research for good reasons. It is concise, readable, and surrounded by excellent libraries for data analysis and statistics. An idea that takes an afternoon in Python can take much longer in a lower-level language, and that speed of thought matters, because most research ideas fail. Building every early experiment like an exchange gateway is a waste.
But Python charges for that convenience. Values carry extra information at runtime, memory is managed for you, and the interpreter keeps working while the program runs. On a message-by-message task like maintaining an order book, those costs become visible.
So firms split the system. Researchers explore in Python. The parts that must be fast run in C++, Rust or specialized hardware, with Python often still coordinating the work and analyzing what comes back. The goal is not to crown a language. It is to put each part of the problem at the right level.
C++ and Rust make different promises
C++ has decades of history in performance-sensitive finance. It hands the programmer direct control over memory and compiles to fast machine code. It also offers many ways to create subtle memory errors, which experienced teams manage through discipline, testing and restrictive coding rules. Rust promises comparable speed with stronger guarantees about memory and concurrency, checked at compile time, so a class of mistakes becomes a build failure rather than a production incident. In exchange the programmer must satisfy a demanding ownership system, and the ecosystem is younger in some financial applications.
Neither language removes the hard parts. A poor data structure stays poor after a rewrite. Allocating memory in the critical path causes delays in either one. Network design, operating-system behaviour and hardware often matter more than the language on the label. Which is why this test compares implementations, not mascots.
Sending the same orders through three engines
The same order-book rules are implemented in Python, C++ and Rust. Each program reads the identical stream of additions, cancellations, modifications and trades. Each must produce the same final book, trade log and checksum before its speed counts for anything. The benchmark warms up, repeats its trials, runs both balanced and stressful order flow, and records the slow tail alongside the average.
The measured result
On the reference machine, replaying two million events on the balanced workload, Python processed 2.8 million events per second. C++ processed 39.7 million, and Rust 41.4 million.
Relative to the Python reference, the compiled implementations ran between 14× and 19× faster, depending on the workload. Between C++ and Rust the difference reached 21 per cent on one workload — and its direction was not stable. Rust led on some workloads, C++ on others. Neither can be called the faster language here.
Every implementation produced a byte-identical trade log, a byte-identical final book snapshot and the same checksum on every stream. That gate is checked before a single timing is reported.
Fourteen to nineteen times is a large number and a boring one. It is roughly what anyone who has written both an interpreted and a compiled inner loop would guess. The interesting results are the two the benchmark was never designed to produce.
Two million events, slowed until you can watch them
Each bar advances at the rate that implementation actually replayed the stream, scaled so the Python reference takes about four and a half seconds. Everything else about the run is identical: same events, same rules, same checksum at the end.
This demonstration needs JavaScript. The figures it animates are the ones in the passage above.
The first surprise: a hash function, not a language
Swap Rust's default hash map for one with a plain integer hash. Rust jumps from 15× the Python baseline to 20×, past the C++ build it had been losing to. Same algorithm, same source file, one line of configuration.
Nothing about Rust changed. What changed is that Rust's standard library defaults to a hash function built to resist an attacker who feeds the program keys chosen to collide. That is a sensible default for a web server parsing untrusted input. It is an odd one for a program hashing sequential order IDs from a private market-data feed, and it costs real throughput. The C++ build never paid it, because its standard library hashes a small integer by leaving it alone.
So a large part of what looked like a gap between two systems languages was a difference in what their standard libraries assume about who is attacking you.
The benchmark measured a library decision and nearly reported it as a language.
That is why the integer-hash build gets its own labelled row rather than folding into the Rust result. Either number alone would have been defensible and misleading.
The second surprise: the clock could not measure what it was asked to
The benchmark set out to report the time taken by a typical single event, alongside throughput. On this machine it cannot, and saying so is more useful than the number would have been.
The machine's clock advances in steps of 41 nanoseconds. Read it twice in a row and it usually returns the same step. The compiled implementations handle a typical order-book event in less time than one step. So their median came back as 41 nanoseconds, which is not a property of the code. It is the resolution of the ruler.
The tails survive this. A slowest-one-per-cent figure of 83 nanoseconds is two steps and means something. Python's 1,291 nanoseconds is thirty-one steps and means a great deal. But the middle figures for C++ and Rust are a floor imposed by the measurement, and the repository refuses to print them as results.
| implementation | M events/s | replay, ms | per-event p99 | vs Python |
|---|---|---|---|---|
| Python 3.12, CPython | 2.76 | 725.3 | 1,291 ns | 1× |
| C++20, clang −O3 | 39.75 | 50.3 | 83 ns | 14× |
| Rust, default hasher | 41.41 | 48.3 | 42 ns | 15× |
| Rust, integer hasher | 54.40 | 36.8 | 42 ns | 20× |
Balanced workload, 2,000,000 events. Median of seven timed replays after two warm-ups. The 42-nanosecond figures are one clock tick — a floor, not a measurement.
What the speed cost
The costs are published raw, with no attempt to combine them into a productivity score, because any such score would be the author's opinion wearing a number's clothes.
The Python version is 618 lines and builds instantly, because there is nothing to build. The C++ version is 630 lines and produces a 78 KiB binary in 0.8 seconds. The Rust version is 871 lines — 40 per cent more than either — and produces a 541 KiB binary in the same 0.8 seconds. It also keeps every safety check in the hot path, and removing them was deliberately not attempted.
Other differences between the three are listed rather than corrected for. Each is a place where a more determined programmer would go faster, and therefore a place where a benchmark author could quietly tilt the result.
The most expensive line of code may be the one you do not need
This is why low-latency engineers stay valuable even as AI writes more code. Producing code is only part of the job. Someone still has to define the exact behaviour, reason about memory and concurrency, and measure the tail. Someone has to read the generated machine code when it matters, and decide whether an optimization is worth its complexity. And saving a microsecond is only valuable when the strategy is sensitive to that microsecond. A long-term investor does not need a server beside an exchange. Plenty of systematic strategies work on minutes, hours or days, and gain far more from reliable data than from microscopic speed.
The sophisticated decision is sometimes to optimize. It is sometimes to stop.
For non-technical investors, this is the hidden machinery behind the price on a screen. Modern markets are not simply places where opinions meet. They are real-time systems in which economics, physics and software design decide whose decision arrives first.
What these numbers are not
They are not a language ranking. They measure four disclosed programs on one disclosed machine, with every documented difference between them listed rather than corrected away.
They are not evidence about production trading systems. Network path, kernel bypass, network-card choice, colocation and strategy design routinely matter more than which language holds the order book. A firm that rewrites a Python book in Rust and keeps a slow network has bought nothing.
And they are not the fastest possible implementation in any of the three languages. Each is the same algorithm written plainly. A team that spent a month on the C++ version would beat all of these, which is exactly why this article is about the method rather than the winner.
One caveat travels with the whole table. This is macOS on Apple silicon, where the operating system controls processor frequency, thermal state and which cores the work lands on. None of that can be pinned down from user space. Runs were not isolated to a core and the machine was not verified idle. Treat a few per cent of between-run variation as noise. Which is another way of saying the 21 per cent gap between C++ and Rust is real, and the smaller ones are not worth arguing about.
Companion repository python-rust-cpp-trading-race, published with the verified results.
Repository specification
Build a publication-quality, fully reproducible GitHub repository named python-rust-cpp-trading-race. Implement and benchmark the same deterministic limit-order-book event-processing kernel in Python, C++ and Rust. The objective is an honest, reproducible comparison on disclosed hardware — not a universal claim that one language is always fastest.
Define one language-neutral specification in SPECIFICATION.md: integer price ticks and quantities; supported add, cancel, modify and execute events; side; order ID uniqueness; price-time priority; partial fills; invalid-event behavior; output trades; final-book snapshot; and checksum algorithm. Generate seeded synthetic event streams with configurable book depth, cancellation rate, crossing rate, burstiness and hot-price concentration. Store a small CI fixture in Git and generate larger benchmark fixtures locally. Use a compact documented binary format so parsing overhead is measured consistently.
Implement a clear reference version in Python 3.12, a C++20 version built with CMake, and a stable-edition Rust version built with Cargo. Use equivalent algorithms and data structures as far as each language permits; document every material difference. Do not call a C++ or Rust library from the Python implementation. Avoid disk I/O inside the measured region. Preallocate where practical and separate parsing, book mutation and checksum benchmarks.
Build one benchmark harness that: verifies all implementations produce identical trade logs, final snapshots and checksums; records CPU model, core count, operating system, compiler/interpreter versions, build flags and power-mode caveats; performs warm-ups; runs repeated randomized-order trials; and reports median plus p95 and p99 across repetitions. Measure throughput and per-event latency for at least three workloads. Do not report nanosecond-level precision that the measurement method cannot support. Include debug and optimized builds only if clearly separated; headline results must use release optimization such as C++ -O3 and Rust --release.
Add correctness tests for order priority, partial execution, cancellations, modifications, invalid events, duplicate IDs, empty books, extreme integer values and replay determinism. Use sanitizers for C++ in a non-benchmark test target and appropriate Rust checks. CI should run correctness tests and a small smoke benchmark, but not enforce unstable performance thresholds on shared runners.
Produce at least five publication-ready SVG and PNG figures: the event-processing pipeline; throughput by language and workload; median/p95/p99 comparison; latency distribution or complementary CDF; and speed-versus-code-complexity summary using disclosed measures such as source lines, build time and binary size. Never convert those measures into a subjective developer-productivity score. Write verified article replacement values to reports/article_values.md and machine-readable results to reports/results.json.
Include README.md, LICENSE, Makefile, SPECIFICATION.md, python/, cpp/, rust/, generator/, benchmarks/, tests/, reports/figures/, and GitHub Actions. Provide make verify for cross-language correctness and make benchmark for the full local experiment. The README should first explain why order-book latency matters, then show the verified benchmark, then discuss fairness and limitations, and finally provide build instructions. Explicitly state that the result measures these implementations on this hardware and is not evidence that a language alone determines production trading performance.
Discussion