Home > Back-end >  How can I prevent this memory leak?
How can I prevent this memory leak?

Time:03-17

Below is a stripped-down version of the problem I'm hitting with memory management in relation to using the Python interpreter from C .

The code as it is below will run properly, but its memory footprint will gradually grow over time. I added a line to manually invoke the Python garbage collection; this didn't solve the issue.

What do I need to change with this code to prevent the growing memory leak?

[edit]: As per the suggestion from below, I've cut down the pythonTest function even further. All it does is create an environment, reset it, and close it. The memory leak persists.

I'm using Python 3.10.2 on Windows 10. C is being compiled by Visual Studio to the C 14 standard. I have OpenAI-Gym version 0.22.0 installed.

void pythonTest(PyObject* inModule)
{
    // Section 1: Get the make function:
    PyObject* pMakeFunc = PyObject_GetAttrString(inModule, "make");

    PyObject* pMakeArgs = PyTuple_New(1);
    PyTuple_SetItem(pMakeArgs, 0, PyUnicode_FromString("LunarLanderContinuous-v2"));

    // Section 2: Get the environment and its functions:
    PyObject* pEnv = PyObject_CallObject(pMakeFunc, pMakeArgs);
    PyObject* pEnvReset = PyObject_GetAttrString(pEnv, "reset");
    PyObject* pEnvStep = PyObject_GetAttrString(pEnv, "step");
    PyObject* pEnvClose = PyObject_GetAttrString(pEnv, "close");
    PyObject* pEnvRender = PyObject_GetAttrString(pEnv, "render");

    // Section 3: Reset the environment to get the initial observation:
    PyObject* pInitialObsArray = PyObject_CallNoArgs(pEnvReset);
    PyObject* pInitialObsListFunc = PyObject_GetAttrString(pInitialObsArray, "tolist");
    PyObject* pInitialObsList = PyObject_CallNoArgs(pInitialObsListFunc);

    // Clear section 3:
    Py_CLEAR(pInitialObsList);
    Py_CLEAR(pInitialObsListFunc);
    Py_CLEAR(pInitialObsArray);

    // Clear section 2: Close the environment, first:
    PyObject_CallNoArgs(pEnvClose);
    Py_CLEAR(pEnvRender);
    Py_CLEAR(pEnvClose);
    Py_CLEAR(pEnvStep);
    Py_CLEAR(pEnvReset);

    Py_CLEAR(pEnv);

    // Clear section 1:
    Py_CLEAR(pMakeArgs);
    Py_CLEAR(pMakeFunc);
}

int main()
{
    Py_Initialize();

    // Get gym module:
    PyObject* pGymName = PyUnicode_FromString("gym");
    PyObject* pModule = PyImport_Import(pGymName);

    // Get garbage collection module and collect function:
    PyObject* pgcName = PyUnicode_FromString("gc");
    PyObject* pgcModule = PyImport_Import(pgcName);
    PyObject* pgcFunction = PyObject_GetAttrString(pgcModule, "collect");

    for (int k = 0; k < 1000000;   k)
    {        
        pythonTest(pModule);
        
        // Manually invoke the garbage collection:
        PyObject* pGCReturn = PyObject_CallNoArgs(pgcFunction);
        auto objectsCollected = PyLong_AsLong(pGCReturn);
        std::cout << "Iteration " << k << " objects collected: " 
            << objectsCollected << std::endl;

        Py_CLEAR(pGCReturn);
    }

    Py_CLEAR(pgcFunction);
    Py_CLEAR(pgcModule);
    Py_CLEAR(pgcName);

    Py_CLEAR(pModule);
    Py_CLEAR(pGymName);

    Py_Finalize();

    return 0;
}

CodePudding user response:

You're creating a new pActionList every time round the loop but you don't seem to be disposing of it.

Generally speaking, although you've described this as a stripped down version of your code, it's still pretty complex. I would say keep on stripping it down until the problem goes away. That should show you where the problem lies.

CodePudding user response:

The problem is neither in Python nor its interface to C . The problem is in Box2D, which is used by some of the OpenAI Gym environments.

I can repeat the above code while creating a different environment that doesn't use Box2D (such as "CartPole-v1") and let it run endlessly without any memory leak. As soon as I put a Box2D environment back in (such as "BipedalWalker-v3" or "LunarLander-v2"), the memory leak comes back.

I can repeat the above process entirely in Python and get the same results. Even with manually running garbage collection after every environment destruction, the memory allocated by the application grows without limit.

The reset function on any environment is where it does a lot of preparation to run and it is where the memory leak occurs. If Box2D environments are created and destroyed endlessly, there is no memory leak. Created, reset, then destroyed? Memory leak.

Thank you all for the help, but this is a bug in the underlying library. I'll need to go submit it there.

  • Related