Lua GC is not collecting automatically

91 views Asked by At

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)

0

There are 0 answers