用 C 和 Rust 分别实现 SQLite 扩展 parse_number():性能对比
parse_number() as a SQLite extension: C vs Rust
四个 SQLite 扩展用 C 和 Rust 实现相同的 parse_number() 行为,测试覆盖类型保留、解析规则与浮点精度。优化后的 C 实现每行 16–19 ns,约为 Rust 版本的两倍速度,也远快于 SQL 的 replace()。普通 C 版本因依赖 strtod() 在小数上偏慢,加入紧凑快速路径后补齐差距;所有版本在 800 万次模糊测试中结果一致。
研究 parse_number() 作为 SQLite 扩展:C 与 Rust 对比 —— 四个 SQLite 扩展分别用 C 和 Rust 实现了相同的 `parse_number()` 行为,测试覆盖类型保留、解析规则和浮点精度。优化后的 C 实现最快,每行 16–19 ns,约为 Rust 版本的两倍速度,也远快于 SQL 的 `replace()`。普通 C 版本在小数上较慢,因为它依赖 `strtod()`,而一条紧凑的快速路径弥补了这一差距;所有版本在 800 万次模糊测试中结果一致。
我遛狗时好奇:写一个 SQLite C 扩展,添加一个 parse_number() 函数,把 -$1,234.56 或 '12.5%' 这样的字符串转换成可求和、可排序的数值,到底有多容易或多难。
我打开了普通的 Claude 聊天(Opus 5.5)并这样提示:
I'm thinking about a SQLite custom function 'parse_number(value)' that strips ',', '$', '£' and '%', handles a leading '-', and returns 'NULL' for anything it can’t parse
Explore the difference between building this as a SQLite extension in C v.s. in Rust - I want to compare performance and code complexity and size of the resulting.so(or whatever) file
这给了我一个扎实的起点——Rust 扩展为 321 KB(使用 Rusqlite)和 298 KB(使用 sqlite-loadable-rs),而 C 扩展只有 14 KB。但 Rust 扩展更快!
原来这是因为 C 的 strtod 对应的 Rust 实现“快两倍”。于是我提示:
Try writing your own C function for parsing text into a decimal number that catches up to or beats the Rust implementation
它做到了:
200 万行数据下每行的净开销,越低越好:
初版 C 新版 C rusqlite sqlite-loadable 混合数据 67 ns 19 ns 46 ns 41 ns 仅整数 25 ns 16 ns 36 ns 32 ns 仅小数 106 ns 18 ns 55 ns 47 ns
最后,我让它把原型改写成一份研究报告,以与 simonw/research 中的其他报告保持一致,提示如下:
Clone simonw/research from GitHub and turn your above investigation into a research report as seen in that repository - you can give me a zip file to download that contains your new folder
这是最终生成的报告。
来源:Simon Willison · simonwillison.net