duckdb-luajit: 同一道数独,5 种写法,快 6 倍
duckdb-luajit 这个扩展,前几篇聊过它能读 DICOM、扫目录、补类型、写存储过程。
这篇换个角度:同一个算法,在 SQL 里能用 5 种方式实现,而且速度差近 6 倍。
测试对象是数独求解器。选它是因为算法小而完整:约束检查 + 回溯剪枝,
几十行代码,能清楚看出「谁来编译、编得多好」对性能的影响。
● ● ●
五种方式
同一个求解算法(位图约束 + 最少候选优先,即 MRV 启发式),五种承载方式:
| # | 方式 | 是什么 |
|---|---|---|
| 1 | 纯 Lua | 直接用 Lua 写,UDF 注册进扩展 |
| 2 | gcc 预编译 .so | C 源码用 gcc -O3 编成动态库,FFI 加载 |
| 3 | tcc 编译 .so | 同一份 C 源码,换 TinyCC 编译 |
| 4 | TCC 内存编译 | 没有 .so,libtcc 现场把 C 源码编进内存 |
| 5 | Rust cdylib | Rust 版同算法,rustc 编成动态库 |
其中第 4 种值得多说一句:它读的是 .c 源码文件,在内存里编译,磁盘上不需要
任何产物。没有 gcc 的机器,一样能在数据库里跑 C。
● ● ●
实测数据
同一道锚点题,同口径(5000 次循环、200 次预热、单进程):
| # | 方式 | ms/题 | 相对纯 Lua |
|---|---|---|---|
| 1 | 纯 Lua | 0.976 | 1.00× |
| 2 | gcc -O3 .so | 0.166 | 快 5.9 倍 |
| 3 | tcc .so | 0.905 | 快 1.08 倍 |
| 4 | TCC 内存编译 | 0.893 | 快 1.09 倍 |
| 5 | Rust cdylib | 0.190 | 快 5.1 倍 |
三件事值得停下来想。
● ● ●
一、脚本语言没你想的那么慢
纯 Lua 0.976ms,gcc 0.166ms,差 5.9 倍。
如果你脑补的是「脚本比 C 慢两个数量级」,这里只有 6 倍。
原因是 LuaJIT。它把热路径编译成机器码,热循环里跑的其实已经是原生指令,
和解释器(比如标准 Lua、CPython)完全是两回事。数独这种单函数反复调用的
场景,正是 JIT 的主场。
● ● ●
二、编译器快,代码不一定快
TCC(TinyCC)以「编译速度最快」出名,但那是编译器的速度,不是它生成代码的
速度。同源码,gcc -O3 比 TCC 快 5.4 倍(0.166 vs 0.905)。
TCC 生成的代码和纯 Lua 的 JIT 代码几乎打平(0.893 vs 0.976)——一个 C
编译器,输出质量约等于 LuaJIT 的热路径。编译器快 ≠ 代码快,是两件事。
● ● ●
三、没有 gcc 也能在数据库里写 C
gcc 最快,但很多机器上没有 gcc。TCC 内存编译给了第三条路:libtcc 把 .c
源码现场编进内存,直接调用,磁盘零产物。
代价是性能垫底(和纯 Lua 持平),换来的是「任何机器都能跑 C」。这在
「别人装个扩展就能用」的场景里,比那 5 倍性能更值钱。
● ● ●
选型方法论
- 1.默认纯 Lua:零编译依赖,
INSTALL即用,0.976ms 对绝大多数场景足够 - 2.追求性能用 C/Rust:gcc -O3 或 rustc 预编译 .so,快 5-6 倍,
代价是目标机器要能编译或分发 .so
- 1.TCC 是兜底:没有 gcc、不想分发 .so,但要跑 C——内存编译,零产物
C 和 Rust 之间的 0.166 vs 0.190 基本是打平的。选哪个看你熟悉什么,
性能不是决定因素。
● ● ●
怎么复现
完整代码在 duckdb-luajit-libs 仓库的 libs/ffi/sudoku/:
- ●
sudoku_solve.c/sudoku_solve.rs:同一算法的 C/Rust 实现 - ●
demo_tcc_embed.lua:LuaJIT 直测,libtcc 内存编译 - ●
demo_tcc_in_duckdb.sql:扩展内 E2E,SQL 里现场编译 - ●
demo_tcc_inline.lua:C 源码直接内嵌在 Lua 里,自包含 - ●
bench_four.lua:五方式统一基准,改两行路径即跑
拉下来、装好扩展,30 秒跑出你自己的对比。
● ● ●
参考来源
- ●duckdb-luajit 扩展:https://github.com/alitrack/duckdb-luajit
- ●duckdb-luajit-libs 库仓库:https://github.com/alitrack/duckdb-luajit-libs
- ●数独 FFI 示例与基准:https://github.com/alitrack/duckdb-luajit-libs/tree/main/libs/ffi/sudoku