alitrack

duckdb-luajit: 同一道数独,5 种写法,快 6 倍

duckdb-luajit 这个扩展,前几篇聊过它能读 DICOM、扫目录、补类型、写存储过程。

这篇换个角度:同一个算法,在 SQL 里能用 5 种方式实现,而且速度差近 6 倍。

测试对象是数独求解器。选它是因为算法小而完整:约束检查 + 回溯剪枝,

几十行代码,能清楚看出「谁来编译、编得多好」对性能的影响。

● ● ●

五种方式

同一个求解算法(位图约束 + 最少候选优先,即 MRV 启发式),五种承载方式:

#方式是什么
1纯 Lua直接用 Lua 写,UDF 注册进扩展
2gcc 预编译 .soC 源码用 gcc -O3 编成动态库,FFI 加载
3tcc 编译 .so同一份 C 源码,换 TinyCC 编译
4TCC 内存编译没有 .so,libtcc 现场把 C 源码编进内存
5Rust cdylibRust 版同算法,rustc 编成动态库

其中第 4 种值得多说一句:它读的是 .c 源码文件,在内存里编译,磁盘上不需要

任何产物。没有 gcc 的机器,一样能在数据库里跑 C。

● ● ●

实测数据

同一道锚点题,同口径(5000 次循环、200 次预热、单进程):

#方式ms/题相对纯 Lua
1纯 Lua0.9761.00×
2gcc -O3 .so0.166快 5.9 倍
3tcc .so0.905快 1.08 倍
4TCC 内存编译0.893快 1.09 倍
5Rust cdylib0.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. 1.默认纯 Lua:零编译依赖,INSTALL 即用,0.976ms 对绝大多数场景足够
  2. 2.追求性能用 C/Rust:gcc -O3 或 rustc 预编译 .so,快 5-6 倍,

代价是目标机器要能编译或分发 .so

  1. 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