I'm kinda new to lua although I've already used this language other times and I've read quite a bit about it. Now I'm using it for the second time as my scripting language for a C++11 project. The first time I used it I had a problem with GC, but I decided to just call lua_gc every few seconds.
Now I want to avoid that to squeeze performance and avoid memory leaks. I have a simple dummy test example using LuaJit and luabridge that executes a lua function twiice.
I'm aware userdata objects must be freed manually, but again, even if I don't return the table to cache in a luaref in C++, it still remains in memory.
Luajit and the C++ code are both compiled with MSVC
Code:
#include <lua.hpp>
#include <iostream>
#include <chrono>
#include "LuaBridge/LuaBridge.h"
#include "LuaBridge/detail/LuaRef.h"
#include <thread>
const int prime_count = 1000000;
void setJITStatus(lua_State* L, bool enabled) {
lua_getglobal(L, "jit");
lua_getfield(L, -1, enabled ? "on" : "off");
lua_pcall(L, 0, 0, 0);
lua_pop(L, 1);
}
bool isJITActive(lua_State* L) {
lua_getglobal(L, "jit");
lua_getfield(L, -1, "status");
if (lua_pcall(L, 0, 1, 0) == LUA_OK) {
bool jitActive = lua_toboolean(L, -1) != 0;
lua_pop(L, 2);
return jitActive;
}
else {
std::cerr << "Error when calling jit.status()." << std::endl;
return false;
}
}
void measureLuaFunction(lua_State* L, const char* luaCode, const char* functionName) {
if (luaL_dostring(L, luaCode) == LUA_OK) {
luabridge::LuaRef luaFunc = luabridge::getGlobal(L, functionName);
// 3 MB in memory before this call
if (luaFunc.isFunction()) {
auto start = std::chrono::high_resolution_clock::now();
luaFunc(prime_count);
// I do another call to the func to see if the memory allocated by the first one is freed while the new one executes, but nop :)
luaFunc(prime_count);
// 21 MB in memory after the second call ends (won't get released unless I call lua_gc)
auto stop = std::chrono::high_resolution_clock::now();
auto duration = std::chrono::duration_cast<std::chrono::milliseconds>(stop - start);
std::cout << "Execution time: " << duration.count() << " milliseconds" << "\n";
}
else {
std::cerr << "Function '" << functionName << "' is not callable." << std::endl;
}
}
else {
std::cerr << "Error at executing lua code." << std::endl;
}
}
int main() {
lua_State* L = luaL_newstate();
luaL_openlibs(L);
const char* luaCode = R"lua(
function isPrime(number)
if number <= 1 then
return false
end
for i = 2, math.sqrt(number) do
if number % i == 0 then
return false
end
end
return true
end
function calculatePrimes(count)
local primes = {}
local i = 2
while #primes < count do
if isPrime(i) then
table.insert(primes, i)
end
i = i + 1
end
-- removing the return statement doesn't have any effect
-- return primes
end
)lua";
// Not sure if I should do this, default values of lua vm should be fine but I had to try
// Right now that commented line of code doesn't seem to have an effect in this test, so that's why it's commented
// luaL_dostring(L, "jit.opt.start('maxmcode=2048', 'maxtrace=2048')");
std::cout << "\nIs Jit Active (0 no, 1 yes): " << isJITActive(L) << std::endl;
measureLuaFunction(L, luaCode, "calculatePrimes");
// This line solves the problem, but brings others
// lua_gc(L, LUA_GCCOLLECT, 0);
std::cout << "Closing...\n";
// Thread sleep time to see if the luz gc is able to do something on its own
std::this_thread::sleep_for(std::chrono::seconds(5));
lua_close(L);
return 0;
}
Now after this research I'm not sure if this is something normal and I just don't understand lua or if I'm doing something wrong. If neccesary I can share the whole project as a zip file.
Things tried:
Removing luabridge from the project and using just lua still gets the issue
Removing the return statement in the lua code doesn't solve this
Removing the table and creting local variables solve the problem (it makes the code useless but hey, at least I know those locals do actually get cleaned when going out of scope)