A tiny C extension for SQLite ended up beating Rust on speed, but only after an AI model replaced a standard C library function with its own number parser. Simon Willison reports that he started the experiment while walking his dog. He wanted to know how hard it would be to add a parse_number() function to SQLite that turns messy strings like -$1,234.56 or 12.5% into numbers you can actually sum and sort.
He gave the whole job to Claude chat running Opus 5.5. The result is a useful case study in AI-assisted performance work, and in how much a single library call can cost you.
🔬 What He Tested
Willison’s spec was short. The function should strip commas, dollar signs, pound signs and percent signs, handle a leading minus sign, and return NULL for anything it can’t parse. He then asked Claude to build it two ways, as a SQLite extension in C and as one in Rust, and to compare three things:
- Runtime performance
- Code complexity
- Size of the compiled extension file
Claude built three versions. One was in C. The other two were in Rust, one using Rusqlite and one using sqlite-loadable-rs, two libraries for writing SQLite extensions in Rust.
📦 Round One: Small C, Fast Rust
The size gap was huge. The C extension came in at 14 KB. The Rust builds were 321 KB with Rusqlite and 298 KB with sqlite-loadable-rs, roughly 20 times bigger.
But Rust won on speed. Claude traced the gap to one function. C’s standard strtod, which turns text into a floating-point number, was the bottleneck. According to Willison’s write-up, Rust’s built-in equivalent is “twice as fast.”
That matches what systems programmers have been saying for years. The classic C library parsers carry a lot of old baggage, like locale handling and general-purpose edge cases. Newer languages ship float parsers built on more modern algorithms.
⚡ Round Two: Claude Writes Its Own Parser
Willison then pushed back. He asked Claude to write its own C function for parsing decimal text that “catches up to or beats the Rust implementation.”
Claude used a technique called Clinger’s fast path. Here’s the idea in plain terms. When a number has few enough digits and a small enough exponent, you can turn it into an exact floating-point value with a single multiply or divide. No slow, careful fallback needed. Most real-world values in a spreadsheet column, like prices and percentages, fall right into that easy case.
Here’s the net cost per row across 2 million rows (lower is better):
| Data | First C | New C | rusqlite | sqlite-loadable |
|---|---|---|---|---|
| Mixed data | 67 ns | 19 ns | 46 ns | 41 ns |
| Integers only | 25 ns | 16 ns | 36 ns | 32 ns |
| Decimals only | 106 ns | 18 ns | 55 ns | 47 ns |
The rewritten C version won every category. On decimals it went from 106 ns to 18 ns, close to a 6x speedup over the first attempt and about 2.6x faster than the quickest Rust build. And it stayed tiny.
🧭 Why It Matters
What stands out here isn’t “C beats Rust.” It’s the workflow. Willison went from a curious question to working code in two languages, benchmarks, a targeted optimization and a finished report, all through chat. In the last step, Claude cloned his
simonw/researchrepo on GitHub and formatted the investigation to match his earlier reports.
A few takeaways for practitioners:
- Profile before you pick a language. The first gap came from one library call, not from C vs Rust.
- Push the AI past the first draft. The real win came from a pointed follow-up prompt, not the opening request.
- Binary size is a real tradeoff. If you ship extensions to constrained environments, 14 KB vs 300 KB matters.
- Use AI for cheap exploration. Comparing two implementations used to take days. Now it’s a side project for a dog walk.
⚠️ Caveats
This is a quick prototype, not a peer-reviewed benchmark. The numbers come from one setup, and a hand-rolled parser has to earn trust. Clinger’s fast path only covers the simple cases, so correctness on unusual inputs (very long decimals, extreme exponents) depends on how carefully the fallback is handled. Anyone thinking of using this in production should test it hard against real data before swapping out strtod.
The Rust builds could probably get faster with tuning too. Nobody asked Claude to optimize them.
The full report, with code and methodology, is in Simon Willison’s research repository. Expect to see more experiments like this. When a benchmarked prototype costs a few prompts, people will run a lot more of them.