Love2D下Lua FFI超大规模计算性能反降问题技术咨询
Lua FFI跨规模性能测试异常问题
核心测试现象
Lua FFI在中等规模函数计算场景下性能更优,但在小规模、超大规模函数计算场景下性能反而慢于Lua原生实现。
本次测试基于Love2D框架开展,目标是验证LuaJIT的Lua FFI库实际性能表现,选取区间素数计数作为算力负载做对照测试(测试代码不做算法严谨性保证,仅保证两种实现计算结果一致、属于高负载数学计算任务)。
测试代码
C侧动态库实现(test.c)
// test.c #include <stdio.h> #include <stdlib.h> #include <math.h> double numberBuffer; double getPrimes(double n) { double count = 0; for (double i = 1; i <= n; i++) { double cap = pow(i, 0.5); for (double num = 2; num <= cap; num++) { if (fmod(i, num) == 0) { count++; break; } } } numberBuffer = count; return numberBuffer; }
Lua侧测试逻辑(main.lua)
-- main.lua local ffi = require("ffi") function loadFFI(name) local dir = love.filesystem.getRealDirectory("bin/" .. name .. ".so") return ffi.load(dir .. "bin/" .. name .. ".so") end local test = loadFFI("test") ffi.cdef[[ double getPrimes(double n); ]] local function getPrimes(n) local count = 0 for i = 1,n do for num = 2, i^(0.5) do if (i % num) == 0 then count = (count + 1) break end end end return count end function love.load() local one, two = 0, 0 local n = 60000 local time = love.timer.getTime() local c = test.getPrimes(n) one = (love.timer.getTime() - time) time = love.timer.getTime() local lua = getPrimes(n) two = (love.timer.getTime() - time) print("n = " .. tostring(n)) print("C", c, (tostring(one * 1000) .. " miliseconds")) print("Lua", lua, (tostring(two * 1000) .. " miliseconds")) end
分规模测试表现
- 小规模数据集:受FFI固定调用开销影响,Lua原生实现性能更优,符合预期
- 中等规模数据集:FFI调用的C实现性能大幅领先Lua原生实现,符合预期
- 超大规模数据集:FFI调用的C实现性能远低于Lua原生实现,出现反常结果
测试结果参考截图:
反常性能反转的核心成因
该现象和FFI本身的机制无关,是测试代码的实现差异、编译选项、LuaJIT的优化特性共同导致的:
- C侧代码本身执行效率偏低
测试用的C代码全程使用double浮点类型作为循环变量,循环内反复调用通用标准库函数pow做开方、fmod做取模,这两个函数为了覆盖全量浮点场景做了大量边界处理,本身执行开销很高。如果编译动态库时没有开启-O2/-O3级别的编译优化,这段C代码的原生执行效率本就不高。 - LuaJIT对热点数值循环有极致优化能力
测试用的Lua版素数计数是典型的可被LuaJIT Trace编译优化的热点代码:循环边界清晰、分支逻辑简单,LuaJIT在执行到足够的循环次数后,会把整段热点循环直接编译为高度优化的本地机器码,还会自动做强度削减优化:比如把i^0.5的通用幂运算替换为轻量的整数平方根计算、把浮点取模替换为效率更高的整数运算,甚至会自动做循环展开优化,最终生成的机器码执行效率远高于未开优化、全用低效浮点逻辑的C代码。 - 不同规模下的耗时占比变化触发拐点
- 小规模计算时:FFI单次调用的固定开销(栈切换、参数类型转换)占总耗时比例高,C代码本身的执行速度优势抵不过固定调用开销,因此Lua实现更快
- 中等规模计算时:FFI调用的固定开销被计算量摊薄,此时Lua代码还未完成JIT预热、热点Trace尚未编译完成,C代码的执行速度优势显现,因此C实现更快
- 超大规模计算时:Lua侧的热点循环已经被LuaJIT完全编译为优化后的本地机器码,执行效率反超未做优化、浮点逻辑开销高的C实现,因此出现C实现更慢的反常结果。
可以通过调整C代码验证该结论:编译C动态库时加上-O3 -march=native优化选项,同时把循环变量替换为整型、用整型开方和取模逻辑替换掉pow/fmod的浮点调用,重新测试就会看到C实现在全规模下性能都领先,不会出现大规模下的性能反转。
内容的提问来源于stack exchange,提问作者JustinLua
相关产品推荐
相关产品推荐

